Merge branch 'dev' into implementation/44376-add-selection-state-logic-and-file-link-creation

pull/11513/head
Eric Schubert 2 years ago
commit 165d6bb444
No known key found for this signature in database
GPG Key ID: 1D346C019BD4BAA2
  1. 6
      Gemfile.lock
  2. 4
      app/contracts/user_preferences/params_contract.rb
  3. 4
      app/controllers/account_controller.rb
  4. 2
      app/controllers/concerns/accounts/current_user.rb
  5. 2
      app/controllers/concerns/accounts/user_login.rb
  6. 2
      app/controllers/concerns/accounts/user_password_change.rb
  7. 14
      app/models/queries/columns/base.rb
  8. 2
      app/models/queries/work_packages.rb
  9. 1
      app/models/queries/work_packages/columns/manual_sorting_column.rb
  10. 38
      app/models/queries/work_packages/columns/typeahead_column.rb
  11. 24
      app/models/query.rb
  12. 2
      app/models/query/highlighting.rb
  13. 35
      app/models/setting/autologin.rb
  14. 7
      app/models/setting/self_registration.rb
  15. 6
      app/views/account/_login.html.erb
  16. 4
      app/views/account/_password_login_form.html.erb
  17. 2
      app/views/admin/settings/work_packages_settings/show.html.erb
  18. 2
      config/constants/settings/definitions.rb
  19. 4
      config/initializers/postgresql_timestamp.rb
  20. 6
      config/locales/crowdin/js-af.yml
  21. 6
      config/locales/crowdin/js-ar.yml
  22. 6
      config/locales/crowdin/js-az.yml
  23. 6
      config/locales/crowdin/js-bg.yml
  24. 24
      config/locales/crowdin/js-ca.yml
  25. 6
      config/locales/crowdin/js-ckb-IR.yml
  26. 6
      config/locales/crowdin/js-cs.yml
  27. 6
      config/locales/crowdin/js-da.yml
  28. 6
      config/locales/crowdin/js-de.yml
  29. 6
      config/locales/crowdin/js-el.yml
  30. 6
      config/locales/crowdin/js-eo.yml
  31. 22
      config/locales/crowdin/js-es.yml
  32. 6
      config/locales/crowdin/js-et.yml
  33. 6
      config/locales/crowdin/js-fa.yml
  34. 6
      config/locales/crowdin/js-fi.yml
  35. 6
      config/locales/crowdin/js-fil.yml
  36. 6
      config/locales/crowdin/js-fr.yml
  37. 6
      config/locales/crowdin/js-he.yml
  38. 6
      config/locales/crowdin/js-hi.yml
  39. 6
      config/locales/crowdin/js-hr.yml
  40. 6
      config/locales/crowdin/js-hu.yml
  41. 6
      config/locales/crowdin/js-id.yml
  42. 6
      config/locales/crowdin/js-it.yml
  43. 6
      config/locales/crowdin/js-ja.yml
  44. 6
      config/locales/crowdin/js-ko.yml
  45. 6
      config/locales/crowdin/js-lol.yml
  46. 6
      config/locales/crowdin/js-lt.yml
  47. 6
      config/locales/crowdin/js-lv.yml
  48. 6
      config/locales/crowdin/js-ne.yml
  49. 6
      config/locales/crowdin/js-nl.yml
  50. 6
      config/locales/crowdin/js-no.yml
  51. 26
      config/locales/crowdin/js-pl.yml
  52. 6
      config/locales/crowdin/js-pt.yml
  53. 6
      config/locales/crowdin/js-ro.yml
  54. 6
      config/locales/crowdin/js-ru.yml
  55. 6
      config/locales/crowdin/js-rw.yml
  56. 6
      config/locales/crowdin/js-si.yml
  57. 6
      config/locales/crowdin/js-sk.yml
  58. 6
      config/locales/crowdin/js-sl.yml
  59. 6
      config/locales/crowdin/js-sv.yml
  60. 6
      config/locales/crowdin/js-th.yml
  61. 6
      config/locales/crowdin/js-tr.yml
  62. 6
      config/locales/crowdin/js-uk.yml
  63. 6
      config/locales/crowdin/js-vi.yml
  64. 178
      config/locales/crowdin/js-zh-TW.yml
  65. 4
      config/locales/crowdin/pl.yml
  66. 40
      config/locales/crowdin/tr.yml
  67. 4
      config/locales/crowdin/zh-TW.yml
  68. 6
      config/locales/js-en.yml
  69. 15
      db/migrate/20221017073431_remove_orphaned_tokens.rb
  70. 40
      db/migrate/20221028070534_change_notification_settings_start_date_due_date_and_overdue_duration_unit_to_days.rb
  71. 64
      db/migrate/migration_utils/column.rb
  72. 12
      docs/api/apiv3/paths/work_package_available_relation_candidates.yml
  73. 4
      docs/enterprise-guide/enterprise-cloud-guide/gdpr-compliance/README.md
  74. 2
      docs/installation-and-operations/installation/docker/README.md
  75. 1
      docs/release-notes/12-3-1/README.md
  76. 29
      docs/release-notes/12-3-2/README.md
  77. 7
      docs/release-notes/README.md
  78. 2
      docs/system-admin-guide/backup/README.md
  79. 44
      docs/system-admin-guide/integrations/nextcloud/README.md
  80. 72
      docs/user-guide/notifications/notification-settings/README.md
  81. 8
      frontend/src/app/core/apiv3/api-v3.service.spec.ts
  82. 7
      frontend/src/app/core/apiv3/api-v3.service.ts
  83. 45
      frontend/src/app/core/apiv3/endpoints/storages/apiv3-storages-paths.ts
  84. 8
      frontend/src/app/core/config/configuration.service.ts
  85. 2
      frontend/src/app/core/current-user/current-user.service.ts
  86. 2
      frontend/src/app/core/days/weekday.service.ts
  87. 25
      frontend/src/app/core/state/attachments/attachments.service.ts
  88. 8
      frontend/src/app/core/state/capabilities/capabilities.service.spec.ts
  89. 27
      frontend/src/app/core/state/capabilities/capabilities.service.ts
  90. 21
      frontend/src/app/core/state/collection-store.ts
  91. 44
      frontend/src/app/core/state/days/day.service.ts
  92. 29
      frontend/src/app/core/state/days/weekday.service.ts
  93. 14
      frontend/src/app/core/state/file-links/file-links.service.ts
  94. 44
      frontend/src/app/core/state/in-app-notifications/in-app-notifications.service.ts
  95. 28
      frontend/src/app/core/state/principals/principals.service.ts
  96. 51
      frontend/src/app/core/state/projects/projects.service.ts
  97. 86
      frontend/src/app/core/state/resource-collection.service.ts
  98. 7
      frontend/src/app/core/state/storage-files/storage-files.service.ts
  99. 9
      frontend/src/app/core/state/storages/storages.service.ts
  100. 61
      frontend/src/app/core/state/views/views.service.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -372,7 +372,7 @@ GEM
rexml
crass (1.0.6)
daemons (1.4.1)
dalli (3.2.2)
dalli (3.2.3)
danger (9.0.0)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
@ -861,7 +861,7 @@ GEM
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.23.0)
parser (>= 3.1.1.0)
rubocop-rails (2.17.1)
rubocop-rails (2.17.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
@ -942,7 +942,7 @@ GEM
temple (0.8.2)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
test-prof (1.0.10)
test-prof (1.0.11)
thor (1.2.1)
tilt (2.0.11)
timecop (0.9.5)

@ -28,8 +28,8 @@
module UserPreferences
class ParamsContract < ::ParamsContract
DATE_ALERT_DURATIONS = [nil, 0, 24, 72, 168].freeze
DATE_ALERT_OVERDUE_DURATIONS = [nil, 0, 72, 168].freeze
DATE_ALERT_DURATIONS = [nil, 0, 1, 3, 7].freeze
DATE_ALERT_OVERDUE_DURATIONS = [nil, 1, 3, 7].freeze
validate :only_one_global_setting,
if: -> { notifications.present? }

@ -140,7 +140,7 @@ class AccountController < ApplicationController
end
def allow_registration?
allow = Setting.self_registration? && !OpenProject::Configuration.disable_password_login?
allow = Setting::SelfRegistration.enabled? && !OpenProject::Configuration.disable_password_login?
invited = session[:invitation_token].present?
get = request.get? && allow
@ -163,7 +163,7 @@ class AccountController < ApplicationController
handle_expired_token token
elsif token.user.invited?
activate_by_invite_token token
elsif Setting.self_registration?
elsif Setting::SelfRegistration.enabled?
activate_self_registered token
else
invalid_token_and_redirect

@ -70,7 +70,7 @@ module Accounts::CurrentUser
if session[:user_id]
# existing session
User.active.find_by(id: session[:user_id])
elsif cookies[OpenProject::Configuration['autologin_cookie_name']] && Setting.autologin?
elsif cookies[OpenProject::Configuration['autologin_cookie_name']] && Setting::Autologin.enabled?
# auto-login feature starts a new session
user = User.try_to_autologin(cookies[OpenProject::Configuration['autologin_cookie_name']])
session[:user_id] = user.id if user

@ -4,7 +4,7 @@ module Accounts::UserLogin
def login_user!(user)
# generate a key and set cookie if autologin
if Setting.autologin? && (params[:autologin] || session.delete(:autologin_requested))
if Setting::Autologin.enabled? && (params[:autologin] || session.delete(:autologin_requested))
set_autologin_cookie(user)
end

@ -71,7 +71,7 @@ module Accounts::UserPasswordChange
end
flash_error_message(log_reason: 'invalid credentials', flash_now:) do
if Setting.brute_force_block_after_failed_logins?
if Setting.brute_force_block_after_failed_logins.to_i > 0
:notice_account_invalid_credentials_or_blocked
else
:notice_account_invalid_credentials

@ -28,7 +28,8 @@
class Queries::Columns::Base
attr_reader :groupable,
:sortable
:sortable,
:displayable
attr_accessor :name,
:sortable_join,
@ -45,6 +46,7 @@ class Queries::Columns::Base
%i(sortable
sortable_join
displayable
groupable
summable
summable_select
@ -73,7 +75,15 @@ class Queries::Columns::Base
end
def sortable=(value)
@sortable = name_or_value_or_false(value)
@sortable = name_or_value_or_false(value)
end
def displayable=(value)
@displayable = value.nil? ? true : value
end
def displayable?
displayable
end
# Returns true if the column is sortable, otherwise false

@ -82,5 +82,7 @@ module Queries::WorkPackages
column Columns::CustomFieldColumn
column Columns::RelationToTypeColumn
column Columns::RelationOfTypeColumn
column Columns::ManualSortingColumn
column Columns::TypeaheadColumn
end
end

@ -32,6 +32,7 @@ class Queries::WorkPackages::Columns::ManualSortingColumn < Queries::WorkPackage
def initialize
super :manual_sorting,
default_order: 'asc',
displayable: false,
sortable: "#{OrderedWorkPackage.table_name}.position NULLS LAST, #{WorkPackage.table_name}.id"
end

@ -0,0 +1,38 @@
#-- 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.
#++
class Queries::WorkPackages::Columns::TypeaheadColumn < Queries::WorkPackages::Columns::WorkPackageColumn
def self.instances(_context = nil)
new :typeahead,
displayable: false,
# This is an ugly hack. When using the typeahead order, the work packages should always be ordered
# by their updated_at. But when asc is specified for typeahead, the updated_at property is to be used
# in desc order.
sortable: ->(table_name = WorkPackage.table_name) { "#{table_name}.updated_at DESC, #{table_name}.updated_at" }
end
end

@ -84,7 +84,7 @@ class Query < ApplicationRecord
def set_default_sort
return if sort_criteria.any?
self.sort_criteria = [['id', 'asc']]
self.sort_criteria = [%w[id asc]]
end
def context
@ -110,7 +110,7 @@ class Query < ApplicationRecord
end
def validate_columns
available_names = available_columns.map(&:name).map(&:to_sym)
available_names = displayable_columns.map(&:name).map(&:to_sym)
(column_names - available_names).each do |name|
errors.add :column_names,
@ -190,11 +190,11 @@ class Query < ApplicationRecord
def available_columns
if @available_columns &&
(@available_columns_project == ((project && project.cache_key) || 0))
(@available_columns_project == (project&.cache_key || 0))
return @available_columns
end
@available_columns_project = (project && project.cache_key) || 0
@available_columns_project = project&.cache_key || 0
@available_columns = ::Query.available_columns(project)
end
@ -205,12 +205,20 @@ class Query < ApplicationRecord
.flatten
end
def self.displayable_columns
available_columns.select(&:displayable?)
end
def self.groupable_columns
available_columns.select(&:groupable)
end
def self.sortable_columns
available_columns.select(&:sortable) + [manual_sorting_column]
available_columns.select(&:sortable)
end
def displayable_columns
available_columns.select(&:displayable?)
end
# Returns an array of columns that can be used to group the results
@ -220,7 +228,7 @@ class Query < ApplicationRecord
# Returns an array of columns that can be used to sort the results
def sortable_columns
available_columns.select(&:sortable) + [manual_sorting_column]
available_columns.select(&:sortable)
end
# Returns a Hash of sql columns for sorting by column
@ -249,7 +257,7 @@ class Query < ApplicationRecord
end
# preserve the order
column_list.map { |name| available_columns.find { |col| col.name == name.to_sym } }.compact
column_list.map { |name| displayable_columns.find { |col| col.name == name.to_sym } }.compact
end
def column_names=(names)
@ -428,7 +436,7 @@ class Query < ApplicationRecord
end
def valid_column_subset!
available_names = available_columns.map(&:name).map(&:to_sym)
available_names = displayable_columns.map(&:name).map(&:to_sym)
self.column_names &= available_names
end

@ -54,7 +54,7 @@ module Query::Highlighting
end
def available_highlighting_columns
@available_highlighting_columns ||= available_columns.select(&:highlightable?)
@available_highlighting_columns ||= displayable_columns.select(&:highlightable?)
end
def highlighted_columns

@ -0,0 +1,35 @@
#-- 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.
#++
class Setting
module Autologin
def self.enabled?
Setting.autologin.presence != 0
end
end
end

@ -36,6 +36,7 @@ class Setting
manual_activation: 2,
automatic_activation: 3
}.freeze
KEYS = VALUES.invert.merge(VALUES.invert.transform_keys(&:to_s)).freeze
def self.values
VALUES
@ -46,7 +47,7 @@ class Setting
end
def self.key(value:)
VALUES.find { |_k, v| v == value || v.to_s == value.to_s }&.first
KEYS[value]
end
def self.disabled
@ -57,6 +58,10 @@ class Setting
key(value: Setting.self_registration) == :disabled
end
def self.enabled?
!disabled?
end
def self.by_email
value key: :activation_by_email
end

@ -39,10 +39,10 @@ See COPYRIGHT and LICENSE files for more details.
<%= styled_text_field_tag 'username', nil, id: 'username-pulldown', tabindex: 1, autocapitalize: 'none' %>
</div>
<div class="form--field-extra-actions">
<% if Setting.autologin? %>
<% if Setting::Autologin.enabled? %>
<label class="form--label-with-check-box">
<%= styled_check_box_tag 'autologin', 1, false, id: 'autologin-pulldown' %> <%= t(:label_stay_logged_in) %></label>
<% elsif Setting.self_registration? %>
<% elsif Setting::SelfRegistration.enabled? %>
<%# show here if autologin is disabled, otherwise below lost_password link %>
<%= link_to t(:label_register),
account_register_path,
@ -60,7 +60,7 @@ See COPYRIGHT and LICENSE files for more details.
<% if Setting.lost_password? %>
<%= link_to t(:label_password_lost), {controller: '/account', action: 'lost_password'} %>
<% end %>
<% if Setting.autologin? && Setting.self_registration? %>
<% if Setting::Autologin.enabled? && Setting::SelfRegistration.enabled? %>
<%# show here if autologin is enabled, otherwise below login field %>
<%= '<br>'.html_safe if Setting.lost_password? %>
<%= link_to t(:label_register),

@ -44,7 +44,7 @@ See COPYRIGHT and LICENSE files for more details.
</div>
</div>
<% if Setting.autologin? %>
<% if Setting::Autologin.enabled? %>
<div class="form--field -no-label">
<div class="form--field-container">
<label class="form--label-with-check-box">
@ -66,7 +66,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= link_to t(:label_password_lost), { controller: '/account', action: 'lost_password' } %>
<br>
<% end %>
<% if Setting.self_registration? %>
<% if Setting::SelfRegistration.enabled? %>
<%= link_to t(:label_register),
'',
title: t(:label_register),

@ -69,7 +69,7 @@ See COPYRIGHT and LICENSE files for more details.
</section>
<fieldset class="form--fieldset"><legend class="form--fieldset-legend"><%= t(:setting_column_options) %></legend>
<%
column_choices = Query.new.available_columns.map { |column|
column_choices = Query.new.displayable_columns.map { |column|
{ caption: column.caption, value: column.name.to_s }
}
%>

@ -977,7 +977,7 @@ Settings::Definition.define do
add :work_package_list_default_columns,
default: %w[id subject type status assigned_to priority],
allowed: -> { Query.new.available_columns.map(&:name).map(&:to_s) }
allowed: -> { Query.new.displayable_columns.map(&:name).map(&:to_s) }
add :work_package_startdate_is_adddate,
default: false

@ -1,2 +1,4 @@
# Use timestampz to create new timestamp columns, so that we get WITH TIME ZONE support
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
end

@ -537,8 +537,8 @@ af:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ af:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ ar:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -580,7 +580,7 @@ ar:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ az:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ az:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ bg:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ bg:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ ca:
no_unread: "No hi ha notificacions sense llegir"
reasons:
mentioned: 'mencionat'
watched: 'observat'
assigned: 'assignat'
watched: 'watcher'
assigned: 'assignee'
responsible: 'responsable'
created: 'creat'
scheduled: 'programat'
@ -575,19 +575,19 @@ ca:
by_project: 'No llegit per projecte'
by_reason: 'Implicació'
inbox: 'Safata d''entrada'
mentioned: 'Mentioned'
watching: 'Observant'
mentioned: 'Mencionat'
watched: 'Watcher'
settings:
change_notification_settings: 'Per veure i canviar la configuració de notificacions, <a target="_blank" href="%{url}">fes clic aquí</a>'
title: "Configuració de notificacions"
notify_me: "Notifica’m"
reasons:
mentioned:
title: 'Mentioned'
title: 'Mencionat'
description: 'Rep una notificació cada vegada que algú em menciona a qualsevol lloc'
assignee: 'Assignat a'
responsible: 'Responsable'
watched: 'Watcher'
watched: 'Observador'
work_package_commented: 'Tots els nous comentaris'
work_package_created: 'Paquets de treball nou'
work_package_processed: 'Tots els canvis d''estat'
@ -595,14 +595,14 @@ ca:
work_package_scheduled: 'Tots els canvis de data'
global:
immediately:
title: 'Participating'
description: 'Notifications for all activities in work packages you are involved in (assignee, accountable or watcher)'
title: 'Participant'
description: 'Notificacions per a totes les activitats en els paquets de treball en els que estàs involucrat (assignat, responsable o observador)'
delayed:
title: 'Non-participating'
description: 'Additional notifications for activities in all projects'
title: 'No participant'
description: 'Notificacions addicionals per activitats en tots els projectes'
date_alerts:
title: 'Date alerts'
description: 'Automatic notifications when important dates are approaching for open work packages you are involved in (assignee, accountable or watcher)'
title: 'Alertes per dates'
description: 'Notificacions automàtiques quan dates importants s''aproximen per a paquets de treball oberts en els quals estàs involucrat (assignat, responsable o observador)'
project_specific:
title: 'Configuracions de notificacions específiques del projecte'
description: 'Aquestes configuracions específiques del projecte anul·len les configuracions per defecte especificades a sobre'

@ -537,8 +537,8 @@ ckb-IR:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ ckb-IR:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ cs:
no_unread: "Žádná nepřečtená oznámení"
reasons:
mentioned: 'zmíněn'
watched: 'sledován'
assigned: 'přiřazeno'
watched: 'watcher'
assigned: 'assignee'
responsible: 'Odpovědný'
created: 'Vytvořeno'
scheduled: 'Naplánováno'
@ -578,7 +578,7 @@ cs:
by_reason: 'Zapojení'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Sledování'
watched: 'Watcher'
settings:
change_notification_settings: 'Chcete-li zobrazit a změnit nastavení oznámení, <a target="_blank" href="%{url}">klikněte zde</a>'
title: "Nastavení oznámení"

@ -536,8 +536,8 @@ da:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -575,7 +575,7 @@ da:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -536,7 +536,7 @@ de:
no_unread: "Keine ungelesenen Benachrichtigungen"
reasons:
mentioned: 'erwähnt'
watched: 'beobachtet'
watched: 'Beobachter'
assigned: 'zugewiesen'
responsible: 'verantwortlich'
created: 'erstellt'
@ -574,8 +574,8 @@ de:
by_project: 'Ungelesen pro Projekt'
by_reason: 'Beteiligung'
inbox: 'Eingang'
mentioned: 'Mentioned'
watching: 'Beobachtet'
mentioned: 'Erwähnt'
watched: 'Beobachter'
settings:
change_notification_settings: 'Um Ihre Benachrichtigungseinstellungen anzuzeigen und zu ändern, <a target="_blank" href="%{url}">klicken Sie hier</a>'
title: "Benachrichtigungseinstellungen"

@ -536,8 +536,8 @@ el:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -575,7 +575,7 @@ el:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ eo:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ eo:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,7 +537,7 @@ es:
no_unread: "No hay notificaciones sin leer"
reasons:
mentioned: 'mencionado'
watched: 'observado'
watched: 'observador'
assigned: 'asignado'
responsible: 'responsable'
created: 'creado'
@ -575,19 +575,19 @@ es:
by_project: 'Sin leer por proyecto'
by_reason: 'Implicación'
inbox: 'Bandeja de entrada'
mentioned: 'Mentioned'
watching: 'Observando'
mentioned: 'Mencionado'
watched: 'Observador'
settings:
change_notification_settings: 'Para ver y cambiar los ajustes de notificaciones, <a target="_blank" href="%{url}">haga clic aquí</a>'
title: "Ajustes de notificación"
notify_me: "Notifícame"
reasons:
mentioned:
title: 'Mentioned'
title: 'Mencionado'
description: 'Recibir una notificación cada vez que un usuario me mencione'
assignee: 'Asignado a'
responsible: 'Responsable'
watched: 'Watcher'
watched: 'Observador'
work_package_commented: 'Todos los nuevos comentarios'
work_package_created: 'Nuevos paquetes de trabajo'
work_package_processed: 'Todos los cambios de estado'
@ -595,14 +595,14 @@ es:
work_package_scheduled: 'Todos los cambios de fecha'
global:
immediately:
title: 'Participating'
description: 'Notifications for all activities in work packages you are involved in (assignee, accountable or watcher)'
title: 'Participado'
description: 'Notificaciones para todas las actividades en paquetes de trabajo en los que está involucrado (asignado, responsable u observador)'
delayed:
title: 'Non-participating'
description: 'Additional notifications for activities in all projects'
title: 'No participando'
description: 'Notificaciones adicionales para actividades en todos los proyectos'
date_alerts:
title: 'Date alerts'
description: 'Automatic notifications when important dates are approaching for open work packages you are involved in (assignee, accountable or watcher)'
title: 'Alertas de fecha'
description: 'Notificaciones automáticas cuando se acercan fechas importantes para paquetes de trabajo abiertos en los que está involucrado (asignado, responsable u observador)'
project_specific:
title: 'Configuración de notificaciones específicas de proyectos'
description: 'Esta configuración específica de proyectos reemplaza los ajustes predeterminados anteriores'

@ -537,8 +537,8 @@ et:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ et:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ fa:
no_unread: "اطلاعیه خوانده نشدهای وجود ندارد"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ fa:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ fi:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ fi:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ fil:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ fil:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,7 +537,7 @@ fr:
no_unread: "Pas de notification non lue"
reasons:
mentioned: 'mentionné'
watched: 'surveillé'
watched: 'observateur'
assigned: 'assigné'
responsible: 'responsable'
created: 'créé'
@ -575,8 +575,8 @@ fr:
by_project: 'Non lu par projet'
by_reason: 'Participation'
inbox: 'Boîte de réception'
mentioned: 'Mentioned'
watching: 'Surveillé'
mentioned: 'Mention'
watched: 'Observateur'
settings:
change_notification_settings: 'Pour afficher et modifier vos paramètres de notification, <a target="_blank" href="%{url}">cliquez ici</a>'
title: "Paramètres de notifications"

@ -537,8 +537,8 @@ he:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -578,7 +578,7 @@ he:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ hi:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ hi:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ hr:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -577,7 +577,7 @@ hr:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ hu:
no_unread: "Nincs olvasatlan értesítés"
reasons:
mentioned: 'megemlített'
watched: 'Megfigyelt'
assigned: 'hozzárendelve'
watched: 'watcher'
assigned: 'assignee'
responsible: 'felelős'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ hu:
by_reason: 'Involvement'
inbox: 'Beérkezett üzenetek'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'Az értesítési beállítások megtekintéséhez és megváltoztatásához <a target="_blank" href="%{url}">kattintson ide</a>'
title: "Értesítési beállítások"

@ -537,8 +537,8 @@ id:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -575,7 +575,7 @@ id:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ it:
no_unread: "Nessuna nuova notifica"
reasons:
mentioned: 'menzionato'
watched: 'guardato'
assigned: 'assegnato'
watched: 'watcher'
assigned: 'assignee'
responsible: 'responsabile'
created: 'creato'
scheduled: 'programmato'
@ -576,7 +576,7 @@ it:
by_reason: 'Coinvolgimento'
inbox: 'Posta In Arrivo'
mentioned: 'Mentioned'
watching: 'Osservazione'
watched: 'Watcher'
settings:
change_notification_settings: 'Per visualizzare e modificare le impostazioni di notifica, <a target="_blank" href="%{url}">fai clic qui</a>'
title: "Impostazioni notifiche"

@ -538,8 +538,8 @@ ja:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ ja:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ ko:
no_unread: "읽지 않는 알림 없음"
reasons:
mentioned: '멘션 된'
watched: '지켜본'
assigned: '할당됨'
watched: 'watcher'
assigned: 'assignee'
responsible: '담당'
created: '생성됨'
scheduled: '예약됨'
@ -575,7 +575,7 @@ ko:
by_reason: '참여'
inbox: '받은 편지함'
mentioned: 'Mentioned'
watching: '지켜보는 중'
watched: 'Watcher'
settings:
change_notification_settings: '알림 설정을 보고 변경하려면 <a target="_blank" href="%{url}">여기를 클릭</a>하세요.'
title: "알림 설정"

@ -536,8 +536,8 @@ lol:
no_unread: "crwdns787684:0crwdne787684:0"
reasons:
mentioned: 'crwdns787686:0crwdne787686:0'
watched: 'crwdns787688:0crwdne787688:0'
assigned: 'crwdns787690:0crwdne787690:0'
watched: 'crwdns836616:0crwdne836616:0'
assigned: 'crwdns836618:0crwdne836618:0'
responsible: 'crwdns787692:0crwdne787692:0'
created: 'crwdns832420:0crwdne832420:0'
scheduled: 'crwdns832422:0crwdne832422:0'
@ -575,7 +575,7 @@ lol:
by_reason: 'crwdns787728:0crwdne787728:0'
inbox: 'crwdns787730:0crwdne787730:0'
mentioned: 'crwdns836454:0crwdne836454:0'
watching: 'crwdns787734:0crwdne787734:0'
watched: 'crwdns836620:0crwdne836620:0'
settings:
change_notification_settings: 'crwdns787736:0%{url}crwdne787736:0'
title: "crwdns787738:0crwdne787738:0"

@ -537,8 +537,8 @@ lt:
no_unread: "Nėra neperskaitytų pranešimų"
reasons:
mentioned: 'paminėtas'
watched: 'stebimas'
assigned: 'paskirta'
watched: 'watcher'
assigned: 'assignee'
responsible: 'atsakingas'
created: 'created'
scheduled: 'scheduled'
@ -578,7 +578,7 @@ lt:
by_reason: 'Pagal įsitraukimą'
inbox: 'Gauti'
mentioned: 'Mentioned'
watching: 'Stebima'
watched: 'Watcher'
settings:
change_notification_settings: 'Norėdami pažiūrėti ir keisti jūsų pranešimų nustatymus, <a target="_blank" href="%{url}">spauskite čia</a>'
title: "Pranešimų nustatymai"

@ -537,8 +537,8 @@ lv:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -577,7 +577,7 @@ lv:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ ne:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ ne:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ nl:
no_unread: "Geen ongelezen meldingen"
reasons:
mentioned: 'vermeld'
watched: 'bekeken'
assigned: 'toegewezen'
watched: 'watcher'
assigned: 'assignee'
responsible: 'verantwoordelijk'
created: 'aangemaakt'
scheduled: 'gepland'
@ -576,7 +576,7 @@ nl:
by_reason: 'Betrokkenheid'
inbox: 'Inkomend'
mentioned: 'Mentioned'
watching: 'Bekijken'
watched: 'Watcher'
settings:
change_notification_settings: 'Om je meldingen instellingen te bekijken en te wijzigen, <a target="_blank" href="%{url}">klik hier</a>'
title: "Instellingen voor meldingen"

@ -537,8 +537,8 @@
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ pl:
no_unread: "Brak nieprzeczytanych powiadomień"
reasons:
mentioned: 'wzmianka'
watched: 'obserwowane'
assigned: 'przypisane'
watched: 'obserwator'
assigned: 'przypisana osoba'
responsible: 'osoba odpowiedzialna'
created: 'utworzono'
scheduled: 'zaplanowano'
@ -577,19 +577,19 @@ pl:
by_project: 'Nieprzeczytane wg projektu'
by_reason: 'Zaangażowanie'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Obserwowane'
mentioned: 'Wzmianka'
watched: 'Obserwator'
settings:
change_notification_settings: 'Aby wyświetlić i zmienić ustawienia powiadomień, <a target="_blank" href="%{url}">kliknij tutaj</a>'
title: "Ustawienia powiadomień"
notify_me: "Powiadom mnie"
reasons:
mentioned:
title: 'Mentioned'
title: 'Wzmianka'
description: 'Otrzymuj powiadomienie za każdym razem, gdy pojawi się o tobie wzmianka gdziekolwiek'
assignee: 'Przypisana osoba'
responsible: 'Osoba odpowiedzialna'
watched: 'Watcher'
watched: 'Obserwator'
work_package_commented: 'Wszystkie nowe komentarze'
work_package_created: 'Nowe pakiety robocze'
work_package_processed: 'Wszystkie zmiany statusu'
@ -597,14 +597,14 @@ pl:
work_package_scheduled: 'Wszystkie zmiany daty'
global:
immediately:
title: 'Participating'
description: 'Notifications for all activities in work packages you are involved in (assignee, accountable or watcher)'
title: 'Uczestnictwo'
description: 'Powiadomienia dla wszystkich aktywności w pakietach roboczych, do których cię zaangażowano (jako osobę: przypisaną, odpowiedzialną, bądź obserwującą)'
delayed:
title: 'Non-participating'
description: 'Additional notifications for activities in all projects'
title: 'Brak uczestnictwa'
description: 'Dodatkowe powiadomienia dla działań we wszystkich projektach'
date_alerts:
title: 'Date alerts'
description: 'Automatic notifications when important dates are approaching for open work packages you are involved in (assignee, accountable or watcher)'
title: 'Data ostrzeżeń'
description: 'Automatyczne powiadomienia, gdy ważne daty zbliżają się do otwartych pakietów roboczych, do których cię zaangażowano (jako osobę: przypisaną, odpowiedzialną bądź obserwującą)'
project_specific:
title: 'Ustawienia powiadomień dla danego projektu'
description: 'Te ustawienia dla danego projektu zastępują ustawienia domyślne powyżej'
@ -914,7 +914,7 @@ pl:
estimatesAndTime: "Szacunki i czas"
other: "Inne"
properties:
assignee: "Przypisany do"
assignee: "Przypisana osoba"
author: "Autor"
createdAt: "Utworzone"
description: "Opis"

@ -536,8 +536,8 @@ pt:
no_unread: "Nenhuma notificação não lida"
reasons:
mentioned: 'mencionado'
watched: 'observado'
assigned: 'atribuído'
watched: 'watcher'
assigned: 'assignee'
responsible: 'responsável'
created: 'criado'
scheduled: 'planejado'
@ -575,7 +575,7 @@ pt:
by_reason: 'Envolvimento'
inbox: 'Caixa de entrada'
mentioned: 'Mentioned'
watching: 'Observando'
watched: 'Watcher'
settings:
change_notification_settings: 'Para visualizar e alterar suas configurações de notificação, <a target="_blank" href="%{url}">clique aqui</a>'
title: "Configurações de notificação"

@ -536,8 +536,8 @@ ro:
no_unread: "Nu sunt notificari necitite"
reasons:
mentioned: 'Menţionate'
watched: 'vizionat'
assigned: 'atribuit'
watched: 'watcher'
assigned: 'assignee'
responsible: 'Responsabil'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ ro:
by_reason: 'Implicare'
inbox: 'Cutie poștală'
mentioned: 'Mentioned'
watching: 'Urmărește'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -536,8 +536,8 @@ ru:
no_unread: "Нет непрочитанных уведомлений"
reasons:
mentioned: 'упомянутый'
watched: 'просмотрено'
assigned: 'назначено'
watched: 'наблюдатель'
assigned: 'назначенный'
responsible: 'подотчетный'
created: 'создано'
scheduled: 'запланировано'
@ -577,7 +577,7 @@ ru:
by_reason: 'Вовлеченность'
inbox: 'Входящие'
mentioned: 'Упомянутые'
watching: 'Смотрит'
watched: 'Наблюдатель'
settings:
change_notification_settings: 'Чтобы просмотреть и изменить настройки уведомлений, <a target="_blank" href="%{url}">нажмите здесь</a>'
title: "Настройки уведомлений"

@ -537,8 +537,8 @@ rw:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ rw:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ si:
no_unread: "නයවත"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -576,7 +576,7 @@ si:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ sk:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -578,7 +578,7 @@ sk:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -536,8 +536,8 @@ sl:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -577,7 +577,7 @@ sl:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -536,8 +536,8 @@ sv:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -575,7 +575,7 @@ sv:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ th:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -575,7 +575,7 @@ th:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -537,8 +537,8 @@ tr:
no_unread: "Okunmamış bildirim yok"
reasons:
mentioned: 'adı geçen, bahsedilen'
watched: 'izlendi'
assigned: 'atanmış'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'oluşturuldu'
scheduled: 'zamanlandı'
@ -576,7 +576,7 @@ tr:
by_reason: 'Katılım'
inbox: 'Gelen Kutusu'
mentioned: 'Mentioned'
watching: 'İzliyor'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Bildirim ayarları"

@ -537,8 +537,8 @@ uk:
no_unread: "Немає непрочитаних сповіщень"
reasons:
mentioned: 'згадано'
watched: 'відстежено'
assigned: 'призначено'
watched: 'watcher'
assigned: 'assignee'
responsible: 'відповідальний'
created: 'створено'
scheduled: 'заплановано'
@ -578,7 +578,7 @@ uk:
by_reason: 'Залучення'
inbox: 'Вхідні'
mentioned: 'Mentioned'
watching: 'Відстеження'
watched: 'Watcher'
settings:
change_notification_settings: 'Щоб переглянути та змінити налаштування сповіщень, <a target="_blank" href="%{url}">натисніть тут</a>'
title: "Налаштування сповіщень"

@ -536,8 +536,8 @@ vi:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -574,7 +574,7 @@ vi:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -23,12 +23,12 @@ zh-TW:
js:
ajax:
hide: "隱藏"
loading: "Loading…"
updating: "Updating…"
loading: "載入中..."
updating: "更新中..."
attachments:
delete: "Delete attachment"
delete: "刪除附件"
delete_confirmation: |
Are you sure you want to delete this file? This action is not reversible.
是否確定要刪除此檔?此操作無法撤銷。
draggable_hint: |
在編輯器欄位上拖動到內聯圖像或引用附件。在繼續拖動時, 將打開已關閉的編輯器欄位。
autocomplete_select:
@ -43,13 +43,13 @@ zh-TW:
You can trigger a backup here. The process can take some time depending on the amount of data (especially attachments) you have. You will receive an email once it's ready.
note: >
A new backup will override any previous one. Only a limited number of backups per day can be requested.
last_backup: Last backup
last_backup_from: Last backup from
title: Backup OpenProject
last_backup: 上次備份
last_backup_from: 上次備份從
title: 備份OpenProject
options: 選項
include_attachments: Include attachments
download_backup: Download backup
request_backup: Request backup
include_attachments: 包含附件
download_backup: 下載備份
request_backup: 要求備份
close_popup_title: "關閉彈出式視窗"
close_filter_title: "關閉篩選器"
close_form_title: "關閉表單"
@ -65,7 +65,7 @@ zh-TW:
button_confirm: "確認"
button_continue: "繼續"
button_copy: "複製"
button_copy_to_other_project: "Copy to other project"
button_copy_to_other_project: "複製到其他專案"
button_custom-fields: "自訂欄位"
button_delete: "删除"
button_delete_watcher: "刪除關注者"
@ -85,10 +85,10 @@ zh-TW:
button_open_fullscreen: "開啟全螢幕檢視"
button_show_cards: "顯示卡片檢視"
button_show_list: "列表檢視"
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: "資料表檢視"
button_show_gantt: "開啟甘特圖檢視"
button_show_fullscreen: "開啟全螢幕檢視"
button_more_actions: "更多動作......"
button_quote: "引言"
button_save: "儲存"
button_settings: "設定"
@ -106,14 +106,14 @@ zh-TW:
caption_rate_history: "Rate history"
clipboard:
browser_error: "您的瀏覽器不支援複製到剪貼簿。請手動複製選取的文字。"
copied_successful: "Successfully copied to clipboard!"
copied_successful: "已成功複製到剪貼簿"
chart:
type: '圖表類型'
axis_criteria: 'Axis criteria'
modal_title: 'Work package graph configuration'
modal_title: '工作包表配置'
types:
line: '折線'
horizontal_bar: 'Horizontal bar'
horizontal_bar: '水平分隔線'
bar: '長條'
pie: '圓餅'
doughnut: '甜甜圈'
@ -133,12 +133,12 @@ zh-TW:
preview: '切換預覽模式'
source_code: '切換標記源模式'
error_saving_failed: '保存文檔失敗, 出現以下錯誤: %{error}'
ckeditor_error: 'An error occurred within CKEditor'
ckeditor_error: 'CKEditor 出現錯誤'
mode:
manual: '切換到標記源模式'
wysiwyg: '切換到所見即所得編輯器'
macro:
error: 'Cannot expand macro: %{message}'
error: '無法展開macro:'
attribute_reference:
macro_help_tooltip: 'This text segment is being dynamically rendered by a macro.'
not_found: 'Requested resource could not be found'
@ -158,7 +158,7 @@ zh-TW:
language_hint: '輸入將用於突出顯示的格式語言 (如果支援)。'
dropdown:
macros: '巨集'
chose_macro: 'Choose macro'
chose_macro: '選擇macro'
toc: '目錄'
toolbar_help: '按一下以選擇小部件並顯示工具列。按兩下以編輯小部件'
wiki_page_include:
@ -187,27 +187,27 @@ zh-TW:
drag_to_activate: "從這裡拖曳欄位來啟用它們"
add_group: "添加屬性組"
add_table: "添加相關工作包的表格"
edit_query: 'Edit query'
edit_query: '編輯查詢'
new_group: '新增群組'
reset_to_defaults: '重設為預設值'
enterprise:
text_reprieve_days_left: "%{days} days until end of grace period"
text_expired: "expired"
text_reprieve_days_left: "天至緩衝期結束"
text_expired: "已過期"
trial:
confirmation: "Confirmation of email address"
confirmation: "確認您的電子郵件地址"
confirmation_info: >
We sent you an email on %{date} to %{email}. Please check your inbox and click the confirmation link provided to start your 14 days trial.
form:
general_consent: >
I agree with the <a target="_blank" href="%{link_terms}">terms of service</a> and the <a target="_blank" href="%{link_privacy}">privacy policy</a>.
invalid_email: "無效的電子郵件"
label_company: "Company"
label_company: "公司"
label_first_name: "名字"
label_last_name: "姓:"
label_domain: "Domain"
label_domain: "網域"
label_subscriber: "訂閱者"
label_maximum_users: "最大活動使用者數"
label_starts_at: "Starts at"
label_starts_at: "開始於"
label_expires_at: "過期於"
receive_newsletter: I want to receive the OpenProject <a target="_blank" href="%{link}">newsletter</a>.
taken_domain: There can only be one active trial per domain.
@ -215,7 +215,7 @@ zh-TW:
taken_email: Each user can only create one trial.
email_not_received: "You did not receive an email? You can resend the email with the link on the right."
try_another_email: "或嘗試使用其他電子郵件"
next_steps: "Next steps"
next_steps: "下一步"
resend_link: "重新發送"
resend_success: "Email has been resent. Please check your emails and click the confirmation link provided."
resend_warning: "Could not resend email."
@ -243,10 +243,10 @@ zh-TW:
confidence: >
We deliver the confidence of a tested and supported enterprise-class project management software - with Open Source and an open mind.
link_quote: "獲取報價"
more_info: "More information"
more_info: "更多資訊"
text: >
The OpenProject Enterprise Edition builds on top of the Community Edition. It includes premium features and professional support mainly aimed at organizations with more than 10 users that manage business critical projects with OpenProject.
unlimited: "Unlimited"
unlimited: "無限"
you_contribute: "Developers need to pay their bills, too. By upgrading to the Enterprise Edition, you will be supporting this open source community effort and contributing to its development, maintenance and continuous improvement."
custom_actions:
date:
@ -306,7 +306,7 @@ zh-TW:
new_features_html: >
The release contains various new features and improvements: <br> <ul class="%{list_styling_class}"> <li>Define your global work week (weekends and non-working days of the week).</li> <li>Calculate duration based on start and finish dates and the possibility to consider non-working days (weekends) in scheduling.</li> <li>Add meaningful tooltips to the most essential actions.</li> <li>Copying a project will also copy file links attached to all work packages.</li> </ul>
label_activate: "啟用"
label_assignee: 'Assignee'
label_assignee: '負責執行者'
label_add_column_after: "在後面增加一欄"
label_add_column_before: "在前面增加一欄"
label_add_columns: "增加欄"
@ -376,7 +376,7 @@ zh-TW:
label_in_more_than: "多於"
label_incoming_emails: "接收到的電子郵件"
label_information_plural: "資訊"
label_invalid: "Invalid"
label_invalid: "無效"
label_import: "匯入"
label_latest_activity: "最新活動"
label_last_updated_on: "最後更新於"
@ -394,7 +394,7 @@ zh-TW:
label_next: "下一個"
label_no_color: "無顏色"
label_no_data: "沒有資料可顯示"
label_no_due_date: "no finish date"
label_no_due_date: "無完成日期"
label_no_start_date: "無開始日期"
label_no_value: "無值"
label_none: "無"
@ -408,8 +408,8 @@ zh-TW:
label_previous: "上一個"
label_per_page: "每頁:"
label_please_wait: "請稍候"
label_project: "Project"
label_project_list: "Projects list"
label_project: "專案"
label_project_list: "專案清單"
label_project_plural: "專案"
label_visibility_settings: "可見度設定"
label_quote_comment: "引述這個評論"
@ -422,7 +422,7 @@ zh-TW:
label_report: "報表"
label_repository_plural: "Repositories"
label_save_as: "保存為"
label_select_project: "Select a project"
label_select_project: "選擇專案"
label_select_watcher: "選擇一位關注者"
label_selected_filter_list: "所選的篩選器"
label_show_attributes: "顯示所有屬性"
@ -438,7 +438,7 @@ zh-TW:
label_public_query: "公開"
label_sum: "總和"
label_sum_for: "總和"
label_total_sum: "Total sum"
label_total_sum: "總計"
label_subject: "主題"
label_this_week: "本週"
label_today: "今日"
@ -464,10 +464,10 @@ zh-TW:
label_unwatch: "取消監看"
label_unwatch_work_package: "取消關注工作項目"
label_uploaded_by: "上傳者"
label_default_queries: "Default"
label_starred_queries: "Favorite"
label_global_queries: "Public"
label_custom_queries: "Private"
label_default_queries: "預設"
label_starred_queries: "最愛"
label_global_queries: "公開"
label_custom_queries: "私人"
label_columns: "欄"
label_attachments: 附加檔
label_drop_files: 將檔案拖曳至此處
@ -489,7 +489,7 @@ zh-TW:
label_upload_counter: "%{count} 個中的 %{done} 個文件已完成"
label_validation_error: "由於下列錯誤,無法儲存工作包:"
label_version_plural: "版本"
label_view_has_changed: "This view has unsaved changes. Click to save them."
label_view_has_changed: "變動未儲存,請點按儲存。"
help_texts:
show_modal: '顯示屬性說明文字項目'
onboarding:
@ -499,14 +499,14 @@ zh-TW:
got_it: '了解'
steps:
help_menu: 'The Help (?) menu provides <b>additional help resources</b>. Here you can find a user guide, helpful how-to videos and more. <br> Enjoy your work with OpenProject!'
members: 'Invite new <b>members</b> to join your project.'
project_selection: 'Please click on one of the demo projects that we have prepared. Demo data is currently only available in English. <br> The general <b>demo project</b> suits best for classical project management, while the <b>Scrum project</b> is better for agile project management.'
quick_add_button: 'Click on the plus (+) icon in the header navigation to <b>create a new project</b> or to <b>invite coworkers</b>.'
sidebar_arrow: "Use the return arrow in the top left corner to return to the project’s <b>main menu</b>."
members: '邀請新 <b>會員</b> 加入您的專案。'
project_selection: '請選擇一個具有有用演示資料的專案開始。 <br> <b>演示專案</b> 最適合傳統專案管理, 而 <b>scrum 專案</b> 是更好的敏捷專案管理。'
quick_add_button: '於頁首點按 (+) 鍵 <b>來建立新的專案</b> 或 <b>來邀請隊員</b>.'
sidebar_arrow: "用左上方的返回鍵,返回至專案的 <b>主選單</b>."
welcome: '觀看三分鐘的介紹導覽, 以瞭解最多 <b>重要功能</b>。 <br> 我們建議完成所有步驟直到結束。您可以隨時重新開始導覽。'
wiki: 'Within the <b>wiki</b> you can document and share knowledge together with your team.'
wiki: '在 <b>wiki</b> 中, 您可以與您的團隊一起記錄和分享知識。'
backlogs:
overview: "Manage your work in the <b>backlogs</b> view."
overview: " 在<b>backlogs</b> 檢視中,管理您的工作。"
sprints: "On the right you have the product backlog and the bug backlog, on the left you have the respective sprints. Here you can create <b>epics, user stories, and bugs</b>, prioritize via drag & drop and add them to a sprint."
task_board_arrow: 'To see your <b>task board</b>, open the sprint drop-down...'
task_board_select: '...and select the <b>task board</b> entry.'
@ -533,26 +533,26 @@ zh-TW:
card: 'Drag work packages horizontally to move them backwards or forwards in time, drag the edges to change start and end dates and even drag them vertically to a different row to assign them to another member.'
notifications:
title: "通知"
no_unread: "No unread notifications"
no_unread: "無未讀通知"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
mentioned: '被標記'
watched: '關注者'
assigned: '負責執行者'
responsible: '負責人'
created: 'created'
scheduled: 'scheduled'
commented: 'commented'
processed: 'processed'
prioritized: 'prioritized'
created: '已建立'
scheduled: '已排程'
commented: '已評論'
processed: '已處理'
prioritized: '優先'
facets:
unread: 'Unread'
unread_title: 'Show unread'
unread: '未讀'
unread_title: '顯示未讀'
all: '全部'
all_title: 'Show all'
all_title: '顯示全部'
center:
label_actor_and: 'and'
label_actor_and: ''
and_more_users:
other: 'and %{count} others'
other: '和 1 個其他'
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'
@ -570,43 +570,43 @@ zh-TW:
link_text: 'Click here to load them'
menu:
accountable: '負責人'
by_project: 'Unread by project'
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
by_project: '未讀(依專案)'
by_reason: "參與\n"
inbox: '收件匣'
mentioned: '被標記'
watched: "關注者\n"
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"
notify_me: "Notify me"
change_notification_settings: '檢視或更改您的通知設定, <a target="_blank" href="%{url}">請按此處</a>'
title: "通知設定"
notify_me: "通知我"
reasons:
mentioned:
title: 'Mentioned'
title: '被標記'
description: 'Receive a notification every time someone mentions me anywhere'
assignee: 'Assignee'
responsible: 'Accountable'
watched: 'Watcher'
work_package_commented: 'All new comments'
work_package_created: 'New work packages'
work_package_processed: 'All status changes'
work_package_prioritized: 'All priority changes'
work_package_scheduled: 'All date changes'
assignee: '負責執行者'
responsible: '負責人'
watched: "關注者\n"
work_package_commented: '新評論'
work_package_created: '新工作包'
work_package_processed: '全部狀態改變'
work_package_prioritized: '全部優先級改變'
work_package_scheduled: '全部日期改變'
global:
immediately:
title: 'Participating'
title: '參與中'
description: 'Notifications for all activities in work packages you are involved in (assignee, accountable or watcher)'
delayed:
title: 'Non-participating'
title: '非參與中'
description: 'Additional notifications for activities in all projects'
date_alerts:
title: 'Date alerts'
title: '日期提醒'
description: 'Automatic notifications when important dates are approaching for open work packages you are involved in (assignee, accountable or watcher)'
project_specific:
title: 'Project-specific notification settings'
description: 'These project-specific settings override default settings above'
add: 'Add setting for project'
already_selected: 'This project is already selected'
remove: 'Remove project settings'
title: '特定專案通知設定'
description: '特別專案通知設定將覆蓋上列標準設定。'
add: '於此專案新增設定'
already_selected: '已選取此專案'
remove: '移除專案設定'
password_confirmation:
field_description: '您需要輸入您的帳戶密碼,以確認此更改。'
title: '確認您的密碼以便繼續'
@ -619,7 +619,7 @@ zh-TW:
default: '-'
subject: '在此輸入主旨'
selection: '請選擇'
description: 'Description: Click to edit...'
description: '描述:點選可編輯'
relation_description: '按一下以便為此關係添加描述'
project:
required_outside_context: >

@ -888,7 +888,7 @@ pl:
attributes:
active: "Aktywne"
assigned_to: "Przypisany do"
assignee: "Przypisany do"
assignee: "Przypisana osoba"
attachments: "Załączniki"
author: "Autor"
base: "Błąd ogólny:"
@ -2772,7 +2772,7 @@ pl:
date: "data"
default: "Domyślnie"
column:
assigned_to: "Przypisany do"
assigned_to: "Przypisana osoba"
type: "Typ"
due_date: "Data zakończenia"
name: "Nazwa"

@ -218,7 +218,7 @@ tr:
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'
work_package_file_links: 'Çalışma paketleri: dosya bağlantıları'
delete:
scheduled: "Silme planlandı ve arka planda gerçekleştirilir. Sonuçtan haberdar edileceksiniz."
schedule_failed: "Proje silinemiyor: %{errors}"
@ -541,9 +541,9 @@ tr:
end_deletion: "Silme işlemi bitti"
ignore_non_working_days: "Çalışma dışı günleri yoksay"
include_non_working_days:
title: 'Working days'
false: 'working days only'
true: 'include non-working days'
title: 'İş Günleri'
false: 'Sadece iş günleri'
true: 'çalışılmayan günleri dahil et'
parent: "Üst"
parent_issue: "Üst"
parent_work_package: "Üst"
@ -748,7 +748,7 @@ tr:
user_preference:
attributes:
pause_reminders:
invalid_range: "can only be a valid date range."
invalid_range: "yalnızca geçerli bir tarih aralığı olabilir."
daily_reminders:
full_hour: "can only be configured to be delivered at a full hour."
notification_settings:
@ -775,7 +775,7 @@ tr:
cannot_be_null: "can not be set to null as start date and finish date are known."
parent:
cannot_be_milestone: "kilometre taşı olamaz."
cannot_be_self_assigned: "cannot be assigned to itself."
cannot_be_self_assigned: "kendisine atanamaz."
cannot_be_in_another_project: "başka bir projede olamaz."
not_a_valid_parent: "geçersizdir."
start_date:
@ -1290,7 +1290,7 @@ tr:
error_no_type_in_project: "Bu proje ile ilişkili bir tür yok. Lütfen Proje ayarlarını kontrol edin."
error_omniauth_registration_timed_out: "Harici bir kimlik doğrulama sağlayıcısı aracılığıyla kayıt zaman aşımına uğradı. Lütfen tekrar deneyin."
error_omniauth_invalid_auth: "Kimlik sağlayıcısından döndürülen kimlik doğrulama bilgileri geçersizdi. Daha fazla yardım için lütfen yöneticinizle iletişime geçin."
error_password_change_failed: 'An error occurred when trying to change the password.'
error_password_change_failed: 'Şifreyi değiştirmeye çalışırken bir hata oluştu.'
error_scm_command_failed: "Depoya erişmeye çalışılırken bir hata oluştu:%{value}"
error_scm_not_found: "Depoda girme veya düzeltme bulunamadı."
error_unable_delete_status: "İş paketi durumu en az bir tarafından kullanılır beri silinemez iş paketi."
@ -1385,7 +1385,7 @@ tr:
menus:
admin:
mail_notification: "E-posta bildirimleri"
mails_and_notifications: "Emails and notification"
mails_and_notifications: "E-postalar ve bildirim"
aggregation: 'Kümelenme'
api_and_webhooks: "API and webhooks"
quick_add:
@ -1505,7 +1505,7 @@ tr:
label_check_uncheck_all_in_column: "Sütunundaki tümü işaretle / işaretini kaldır"
label_check_uncheck_all_in_row: "Sıralı tümü işaretle / işaretini kaldır"
label_child_element: "Alt eleman"
label_chronological_order: "Oldest first"
label_chronological_order: "Önce en eski"
label_close_versions: "Tamamlanmış sürümleri kapat"
label_closed_work_packages: "kapalı"
label_collapse: "Daralt"
@ -1555,7 +1555,7 @@ tr:
label_diff: "fark"
label_diff_inline: "satır içi"
label_diff_side_by_side: "yan yana"
label_digital_accessibility: 'Digital accessibility (DE)'
label_digital_accessibility: 'Dijital erişilebilirlik (DE)'
label_disabled: "devre dışı"
label_display: "Göster"
label_display_per_page: "Sayfa başına: %{value}"
@ -1678,7 +1678,7 @@ tr:
lable_membership_updated: 'Üye güncellendi'
label_menu_badge:
pre_alpha: 'pre-alpha'
alpha: 'alpha'
alpha: 'alfa'
beta: 'beta'
label_menu_item_name: "Menü öğesinin ismi"
label_message: "İleti"
@ -1777,7 +1777,7 @@ tr:
label_project_new: "Yeni proje"
label_project_plural: "Projeler"
label_project_settings: "Proje Ayarları"
label_project_storage_plural: "Storages"
label_project_storage_plural: "Depolamalar"
label_projects_storage_information: "%{count} proje %{storage} disk alanı kullanıyor"
label_project_view_all: "Tüm projeleri görüntüle"
label_project_show_details: "Proje detaylarını göster"
@ -1944,7 +1944,7 @@ tr:
label_workflow: "İş akışı"
label_workflow_plural: "İş Akışları"
label_workflow_summary: "Özet"
label_working_days: "Working days"
label_working_days: "İş Günleri"
label_x_closed_work_packages_abbr:
one: "1 kapalı"
other: "%{count} kapatıldı"
@ -1995,7 +1995,7 @@ tr:
digests:
including_mention_singular: 'including a mention'
including_mention_plural: 'including %{number_mentioned} mentions'
unread_notification_singular: '1 unread notification'
unread_notification_singular: 'Okunmamış 1 bildirim'
unread_notification_plural: '%{number_unread} unread notifications'
you_have: 'You have'
logo_alt_text: 'Logo'
@ -2005,7 +2005,7 @@ tr:
center: 'To notification center'
see_in_center: 'See comment in notification center'
settings: 'Change email settings'
salutation: 'Hello %{user}'
salutation: 'Merhaba %{user}'
work_packages:
created_at: 'Created at %{timestamp} by %{user} '
login_to_see_all: 'Log in to see all notifications.'
@ -2021,7 +2021,7 @@ tr:
mentioned: 'Mentioned'
subscribed: 'tüm'
prefix: 'Received because of the notification setting: %{reason}'
see_all: 'See all'
see_all: 'Tümünü gör'
updated_at: 'Updated at %{timestamp} by %{user}'
mail_body_account_activation_request: "Yeni bir kullanıcı ( %{value}) kaydoldu. Hesap onayınızı bekliyor:"
mail_body_account_information: "Hesap bilgilerinizi giriniz"
@ -2034,7 +2034,7 @@ tr:
mail_body_backup_token_warning: Bu siz değildiyseniz, hemen OpenProject'te oturum açın ve yeniden sıfırlayın.
mail_body_incoming_email_error: The email you sent to OpenProject could not be processed.
mail_body_incoming_email_error_in_reply_to: "At %{received_at} %{from_email} wrote"
mail_body_incoming_email_error_logs: "Logs"
mail_body_incoming_email_error_logs: "Günlükler"
mail_body_lost_password: "Parolanızı değiştirmek için aşağıdaki bağlantıyı tıklayın:"
mail_body_register: "Welcome to %{app_title}. Please activate your account by clicking on this link:"
mail_body_register_header_title: "Proje üyesi davet e-postası"
@ -3081,7 +3081,7 @@ tr:
my_registered_applications: "OAuth uygulamalarını kayıtedin"
oauth_client:
urn_connection_status:
connected: "Connected"
connected: "Bağlandı"
error: "Hata"
failed_authorization: "Authorization failed"
labels:
@ -3123,7 +3123,7 @@ tr:
http:
request:
failed_authorization: "The server side request failed authorizing itself."
missing_authorization: "The server side request failed due to missing authorization information."
missing_authorization: "Yetkilendirme bilgileri eksik olduğundan sunucu tarafı isteği başarısız oldu."
response:
unexpected: "Unexpected response received."
unexpected: "Beklenilmeyen bir yanıt alındı."
you: sen

@ -68,7 +68,7 @@ zh-TW:
add_token: "上傳企業版support token"
delete_token_modal:
text: "Are you sure you want to remove the current Enterprise Edition token used?"
title: "Delete token"
title: "刪除Token"
replace_token: "替換當前 support token"
order: "Order Enterprise on-premises Edition"
paste: "貼上您的企業版support token"
@ -78,7 +78,7 @@ zh-TW:
book_now: '立即預約'
get_quote: '獲取報價'
buttons:
upgrade: "Upgrade now"
upgrade: "立即更新"
contact: "Contact us for a demo"
enterprise_info_html: "is an Enterprise <strong class='icon-medal'></strong> feature."
upgrade_info: "Please upgrade to a paid plan to activate and start using it in your team."

@ -582,8 +582,8 @@ en:
no_unread: "No unread notifications"
reasons:
mentioned: 'mentioned'
watched: 'watched'
assigned: 'assigned'
watched: 'watcher'
assigned: 'assignee'
responsible: 'accountable'
created: 'created'
scheduled: 'scheduled'
@ -621,7 +621,7 @@ en:
by_reason: 'Involvement'
inbox: 'Inbox'
mentioned: 'Mentioned'
watching: 'Watching'
watched: 'Watcher'
settings:
change_notification_settings: 'To view and change your notification settings, <a target="_blank" href="%{url}">click here</a>'
title: "Notification settings"

@ -1,10 +1,25 @@
require_relative "./migration_utils/column"
class RemoveOrphanedTokens < ActiveRecord::Migration[7.0]
def up
Token::Base.where.not(user_id: User.select(:id)).delete_all
# Make sure we have bigint columns on both sides so the foreign key can be added.
# It could be that they are of type numeric if the data was migrated from MySQL once.
change_column_type! :users, :id, :bigint
change_column_type! :tokens, :user_id, :bigint
add_foreign_key :tokens, :users
User.reset_column_information
Token::Base.reset_column_information
end
def down
remove_foreign_key :tokens, :users
end
def change_column_type!(table, column, type)
Migration::MigrationUtils::Column.new(connection, table, column).change_type! type
end
end

@ -0,0 +1,40 @@
class ChangeNotificationSettingsStartDateDueDateAndOverdueDurationUnitToDays < ActiveRecord::Migration[7.0]
def change
change_table :notification_settings, bulk: true do |t|
t.change_default :start_date, from: 24, to: 1
t.change_default :due_date, from: 24, to: 1
end
reversible do |dir|
dir.up do
update_durations_from_hours_to_days
end
dir.down do
update_durations_from_days_to_hours
end
end
end
def update_durations_from_hours_to_days
execute <<~SQL.squish
UPDATE
notification_settings
SET
start_date = CASE WHEN start_date IS NOT NULL THEN start_date / 24 END,
due_date = CASE WHEN due_date IS NOT NULL THEN due_date / 24 END,
overdue = CASE WHEN overdue IS NOT NULL THEN overdue / 24 END
SQL
end
def update_durations_from_days_to_hours
execute <<~SQL.squish
UPDATE
notification_settings
SET
start_date = CASE WHEN start_date IS NOT NULL THEN start_date * 24 END,
due_date = CASE WHEN due_date IS NOT NULL THEN due_date * 24 END,
overdue = CASE WHEN overdue IS NOT NULL THEN overdue * 24 END
SQL
end
end

@ -0,0 +1,64 @@
#-- 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.
#++
module Migration
module MigrationUtils
class Column
attr_reader :connection, :table, :name, :ar_column
def initialize(connection, table, name)
@connection = connection
@table = table
@name = name
@ar_column = connection.columns(table.to_s).find { |col| col.name == name.to_s }
raise ArgumentError, "Column not found: #{field}" if ar_column.nil?
end
def change_type!(type)
return if self.type == type.to_s
connection.change_column table, name, type
return if id_seq_name.nil?
connection.execute <<~SQL.squish
ALTER SEQUENCE #{id_seq_name} AS #{type};
SQL
end
def id_seq_name
@id_seq_name ||= connection.serial_sequence table, name
end
def type
ar_column.sql_type.to_s
end
end
end
end

@ -18,7 +18,7 @@ get:
type: integer
- description: |-
JSON specifying filter conditions.
Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint.
Accepts the same filters as the [work packages](https://www.openproject.org/docs/api/endpoints/work_packages/) endpoint.
example: '[{ "status_id": { "operator": "o", "values": null } }]'
in: query
name: filters
@ -39,6 +39,16 @@ get:
required: false
schema:
type: string
- description: |-
JSON specifying sort criteria.
Accepts the same sort criteria as the [work packages](https://www.openproject.org/docs/api/endpoints/work_packages/) endpoint.
example: '[["status", "asc"]]'
in: query
name: sortBy
required: false
schema:
default: '[["id", "asc"]]'
type: string
responses:
'200':
content:

@ -22,9 +22,9 @@ OpenProject cloud environment is hosted on a logically isolated virtual cloud at
We offer secure hosting of your OpenProject cloud also in a German data center on request. Please [contact us](https://www.openproject.org/contact/).
### Data backups and end-to-end https encryption
### Data backups and https encryption
OpenProject cloud environment is continuously backing up user data with data at rest being fully encrypted with AES-256. Each individual instance is logically separated and data is persisted in a unique database schema, reducing the risk of intersection or data leaks between instances.
OpenProject cloud environment is continuously backing up data with data encrypted in transit (via TLS/https) and at rest (files, database (including backups) via AES-256). Each individual instance is logically separated and data is persisted in a unique database schema, reducing the risk of intersection or data leaks between instances.
### Access to data and infrastructure

@ -356,7 +356,7 @@ described above.
Assuming the desired *server name* is `openproject.example.com` the configuration
will look like this:
> **Note:** There is [another example](../packaged/#external-ssl-tls-termination) for external SSL/TLS termination for **packaged** installations
> **Note:** There is [another example](../packaged/#external-ssltls-termination) for external SSL/TLS termination for **packaged** installations
```
<VirtualHost *:80>

@ -18,7 +18,6 @@ The release contains several bug fixes and we recommend updating to the newest v
- Fixed: Frontend including editor and time logging unusable when there are many activities \[[#40373](https://community.openproject.com/wp/40373)\]
- Fixed: Attachments are not going to be copied, when using "Copy to other project" function \[[#43005](https://community.openproject.com/wp/43005)\]
- Fixed: BIM edition unavailable on Ubuntu 22.04 packaged installation \[[#43531](https://community.openproject.com/wp/43531)\]
- Fixed: Custom fields are enabled on project creation \[[#43763](https://community.openproject.com/wp/43763)\]
- Fixed: "Reorder values alphabetically" does not work reliably \[[#43832](https://community.openproject.com/wp/43832)\]
- Fixed: Time and costs: Project filter set to "is not (includes subprojects)" not working as expected \[[#44217](https://community.openproject.com/wp/44217)\]

@ -0,0 +1,29 @@
---
title: OpenProject 12.3.2
sidebar_navigation:
title: 12.3.2
release_version: 12.3.2
release_date: 2022-10-26
---
# OpenProject 12.3.2
Release date: 2022-10-26
We released [OpenProject 12.3.2](https://community.openproject.com/versions/1608).
The release contains several bug fixes and we recommend updating to the newest version.
<!--more-->
#### Bug fixes and changes
- Fixed: Multiples Identicals Webhooks are sent for each WP change applied, not considering the Aggregated WorkPackage Journal \[[#44158](https://community.openproject.com/wp/44158)\]
- Fixed: Moving a week-days-only WP on Gantt chart and falling its end-date to a non-working date is not possible \[[#44501](https://community.openproject.com/wp/44501)\]
- Fixed: Migration to 12.3.1 fails with Key columns "user_id" and "id" are of incompatible types: numeric and bigint. \[[#44634](https://community.openproject.com/wp/44634)\]
- Fixed: rake assets:precompile fails with NameError: uninitialized constant ActiveRecord::ConnectionAdapters::PostgreSQLAdapter \[[#44635](https://community.openproject.com/wp/44635)\]
#### Contributions
A big thanks to community members for reporting bugs and helping us identifying and providing fixes.
Special thanks for reporting and finding bugs go to
Nico Aymet, Johannes Zellner

@ -14,6 +14,13 @@ Stay up to date and get an overview of the new features included in the releases
<!--- New release notes are generated below. Do not remove comment. -->
<!--- RELEASE MARKER -->
## 12.3.2
Release date: 2022-10-26
[Release Notes](12-3-2/)
## 12.3.1
Release date: 2022-10-24

@ -63,7 +63,7 @@ It is sometimes good to have backups on other locations (e.g. local vs cloud). Y
1. The Token must be already generated and stored in a secure keystore
2. The API Key must be known and stored in a secure keystore
You could use our [example bash script](/script/backup-via-apiv3.sh) and integrate it in your crond for running it daily.
You could use our [example bash script](./script/backup-via-apiv3.sh) and integrate it in your crond for running it daily.
## Troubleshooting

@ -33,7 +33,7 @@ Please note these minimum version requirements for the integration to work:
- OpenProject version 12.2 (or above)
- Nextcloud version 22 (or above)
- The [OpenProject integration](https://apps.nextcloud.com/apps/integration_openproject) app, version 2.0.0 (or above)
- The [OpenProject Integration](https://apps.nextcloud.com/apps/integration_openproject) app, version 2.0.0 (or above)
@ -49,13 +49,13 @@ Navigate to the Nextcloud app store by clicking on *your avatar in the top right
![Go to the Nextcloud app store ](apps-in-top-right-menu.png)
On the left menu, click on **Integration** and look for the "[OpenProject integration](https://apps.nextcloud.com/apps/integration_openproject)" app.
On the left menu, click on **Integration** and look for the "[OpenProject Integration](https://apps.nextcloud.com/apps/integration_openproject)" app.
You can also use the search field in the top right corner and type "OpenProject" to find it. Once you have found the app, click the **Download and enable** button.
![Download and enable the OpenProject integration app](Nextcloud_OpenProject_integration_app.png)
![Download and enable the OpenProject Integration app](Nextcloud_OpenProject_integration_app.png)
Once the OpenProject integration app is downloaded and enabled, navigate to the settings page by clicking on *your avatar in the top right corner → Settings*. On the left-side menu, click on **OpenProject integration**
Once the OpenProject Integration app is downloaded and enabled, navigate to the settings page by clicking on *your avatar in the top right corner → Settings*. On the left-side menu, click on **OpenProject Integration**
In the configuration page that appears, you'll see a blank text field titled **OpenProject host**. Enter the address of the OpenProject instance you would like to connect with Nextcloud (including "https://").
@ -63,6 +63,8 @@ In the configuration page that appears, you'll see a blank text field titled **O
Click on the **Save** button.
> **Note:** If the OpenProject host cannot be added, you may check the [Troubleshooting](#troubleshooting) section at the bottom of this page
The next part of the setup will require you to enter OpenProject OAuth values here, but before we do that, you will need to generate them in OpenProject. To do so, navigate to your OpenProject instance in a new browser tab.
#### 2. Create a Nextcloud file storage in your OpenProject instance
@ -97,7 +99,7 @@ Note that OpenProject has automatically generated an OAuth **client ID** and a *
![OpenProject generates OAuth values to copy over to Nextcloud](OP-OAuth-values.png)
Go back to the browser tab where you were configuring the **OpenProject integration** app. (We recommend you have two browser tabs open: the current one with OpenProject and the former one with Nextcloud).
Go back to the browser tab where you were configuring the **OpenProject Integration** app. (We recommend you have two browser tabs open: the current one with OpenProject and the former one with Nextcloud).
Copy the two generated values (client ID and secret) from the OpenProject tab to the respective fields in Nextcloud, namely **OpenProject OAuth client ID** and **OpenProject OAuth client secret**.
@ -123,7 +125,7 @@ Once you hava entered the client ID and client secrets on this page, click on **
![Integration successfully completed on the OpenProject end](Nextcloud-set-up-in-OP.png)
The **OpenProject integration** page on your Nextcloud tab should also indicate that the integration is complete with three green check marks.
The **OpenProject Integration** page on your Nextcloud tab should also indicate that the integration is complete with three green check marks.
![Integration successfully set up on the Nextcloud end, three green checks visible](3_2_05-NC_Success.png)
@ -181,3 +183,33 @@ Deleting a file storage at an instance level deletes the Nextcloud integration c
## Using the integration
Once the file storage is added and enabled for projects, your users are able to take full advantage of the integration between Nextcloud and OpenProject. For more information on how to link Nextcloud files to work packages in OpenProject and access linked work packages in Nextcloud, please refer to [Using the Nextcloud integration](../../../user-guide/nextcloud-integration/).
## Troubleshooting
On Nextcloud inside the OpenProject Integration App when adding the OpenProject host it shows the error **"Please enter a valid OpenProject hostname"**.
- If you are on a local network with your Nextcloud and OpenProject hosts, it might be necessary to explicitly allow local remote servers by setting a system configuration flag on your Nextcloud host's command line. Use the command `sudo -u www-data php occ config:system:set allow_local_remote_servers --value 1` in order to change the setting for Nextcloud.
- To test the connection between OpenProject and Nextcloud, you can use the following methods from your OpenProject host's command line:
```bash
curl --location --request GET 'https://nextcloud.example.com/index.php/apps/integration_openproject/check-config' --header 'Authorization: foo'
# If Nextcloud OpenProject Integration is integrated correctly the response should look like the following
{"user_id":"","authorization_header":foo}
```
```bash
curl -H 'OCS-APIRequest:true' -H 'Accept:application/json' https://nextcloud.example.com/nextcloud/ocs/v2.php/cloud/capabilities
# If Nextcloud is setup correctly the response should look like the following. Pay special attention to current Nextcloud version, which in this example here is "24.0.6". At the time of writing this documentation the minimum version of Nextcloud is 22.
{"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},"data":{"version":{"major":24,"minor":0,"micro":6,"string":"24.0.6","edition":"","extendedSupport":false},"capabilities":{"bruteforce":{"delay":0},"metadataAvailable":{"size":["\/image\\\/.*\/"]},"theming":{"name":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"a safe home for all your data","color":"#0082c9","color-text":"#ffffff","color-element":"#0082c9","color-element-bright":"#0082c9","color-element-dark":"#0082c9","logo":"https:\/\/nextcloud.example.com\/nextcloud\/core\/img\/logo\/logo.svg?v=0","background":"https:\/\/nextcloud.example.com\/nextcloud\/core\/img\/background.png?v=0","background-plain":false,"background-default":true,"logoheader":"https:\/\/nextcloud.example.com\/nextcloud\/core\/img\/logo\/logo.svg?v=0","favicon":"https:\/\/nextcloud.example.com\/nextcloud\/core\/img\/logo\/logo.svg?v=0"}}}}}
```
If you run into any new issues or you cannot solve your integration please use our [Support Installation & Updates forum](https://community.openproject.org/projects/openproject/forums/9) or if you have an Enterprise subscription, please contact us at Enterprise Support.

@ -5,26 +5,66 @@ sidebar_navigation:
description: In-app notification settings in OpenProject
keywords: notifications settings
---
# Notification settings
# Notification Settings
To access the in-app notification settings. Please left-click the **Notification settings** button in the upper right side of the [notification center](..) or navigate via **My account > Notification settings**.
You can configure how and for what events you wish to be notified through Notification Center. To access these settings, you can either click on **_your avatar on the top right corner → My account → Notification settings_** or click on **Notification Settings** on the top right corner of Notification center.
![notification-settings](notification-settings.png)
>> IMG
In the **Notification settings** you can fine-tune what you are notified about:
## Participating
- **I am @mentioned**: You can switch notification on/off when somebody tags you in a comment (i.e. @YOU)
- **Assigned to me or accountable:** You can switch notification on/off when you are either assigned or made accountable for a task, for example.
- **Also notify me for:** Here you can further finetune under what circumstances you would like to be notified, or not:
- Updates on watched items:
- New work packages
- All status changes
- All date changes
- All priority changes
- All new comments
You participate in a work package by either being [mentioned](../../work-packages/edit-work-package/#-notification-mention), by watching it (being on the _Watchers_ list) or by being designated assignee or accountable.
When someone [mentions](../../work-packages/edit-work-package/#-notification-mention) you (@xxx) in e.g. a work package description or comment you will receive an in-app notification.
By default, you will be notified of all activities in work packages in which you participate. However, you can choose to disable these notifications for work packages for which you are assignee or accountable by unchecking these options:
Additionally, you can also add **project specific notification settings** by clicking on the link **Add settings for project**.
>> IMG
By default you do not receive any notifications about your own changes. In addition to the in-app notifications, you will also get a once-a-day summary of all notifications as **[Email reminders](../../../getting-started/my-account#email-reminders)**.
You cannot disable notifications for when you are mentioned (since the goal of mentioning you is to get your attention) or for work packages that you are watching. For the latter, you may disable further notifications simply by unwatching those work package.
> Info: Modifying these settings might result in your missing updates and changes that are relevant to you. We do not recommend changing them unless you are absolutely certain of the consequences.
## Date alerts
Starting 12.4, Open Project offers notification for date alerts. Please note that this is an Enterprise feature.
> **Note**: Date alerts are a Premium Feature and can only be used with [Enterprise cloud](../../../enterprise-guide/enterprise-cloud-guide/) or [Enterprise on-premises](../../../enterprise-on-premises-guide/). An upgrade from the free Community Edition is easy and helps support OpenProject.
Date alerts allow you to receive a notification when a start date or a finish date is approaching for a work package you are participating in (that is, for which you assignee, accountable or a watcher).
>> IMG
For each date, you can choose to be alerted the same day, a day before, 3 days before or a week before.
You can also choose to receive a recurring notification (everyday, every 3 days or every week) for work package that are overdue.
## Non-participating
You can also chose to get notifications for specific events concerning work packages in which you are not participating.
You can be notified of:
- New work package
- Status changes
- Date changes
- Priority changes
- New comment
> **Info:** Please note that these apply to _all_ work packages in _all_ of your projects. Enabling lots of these can result in your receiving too many irrelevant notifications. Please use this feature with parsimony.
## Project-specific notifications
In some cases, you may wish to fine-tune your notification settings at a project-level.
This might be because you are more active in certain projects than others, or because certain events (like new work packages or date alerts) might be more important to you than others (like priority changes).
To add project-specific notification settings, first click on **+ Add setting for project** and select a project.
Once you do so, you will see a table with a column for your selected project, and a list of events for which you wish to be notified. You can now select and unselect from this list as you please.
>> IMG
> **Info**: These project-specific settings will override any global settings above. You can use these settings if you find you receive not enough or too many notifications for a particular project.
## Email reminders
You can supplement these in-app notifications with email reminders, either at specific times of the day or immediately when someone mentions you. For more information, please read our guide on **[Email reminders](../../../getting-started/my-account#email-reminders)**.

@ -26,7 +26,10 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import { TestBed, waitForAsync } from '@angular/core/testing';
import {
TestBed,
waitForAsync,
} from '@angular/core/testing';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { States } from 'core-app/core/states/states.service';
@ -35,8 +38,7 @@ describe('APIv3Service', () => {
let service:ApiV3Service;
beforeEach(waitForAsync(() => {
// noinspection JSIgnoredPromiseFromCall
TestBed.configureTestingModule({
void TestBed.configureTestingModule({
providers: [
States,
PathHelperService,

@ -62,6 +62,7 @@ import { ApiV3NotificationsPaths } from 'core-app/core/apiv3/endpoints/notificat
import { ApiV3ViewsPaths } from 'core-app/core/apiv3/endpoints/views/apiv3-views-paths';
import { Apiv3BackupsPath } from 'core-app/core/apiv3/endpoints/backups/apiv3-backups-path';
import { ApiV3DaysPaths } from 'core-app/core/apiv3/endpoints/days/api-v3-days-paths';
import { Apiv3StoragesPaths } from 'core-app/core/apiv3/endpoints/storages/apiv3-storages-paths';
@Injectable({ providedIn: 'root' })
export class ApiV3Service {
@ -80,6 +81,9 @@ export class ApiV3Service {
// /api/v3/documents
public readonly documents = this.apiV3CollectionEndpoint('documents');
// /api/v3/file_links
public readonly file_links = this.apiV3CollectionEndpoint('file_links');
// /api/v3/notifications
public readonly notifications = this.apiV3CustomEndpoint(ApiV3NotificationsPaths);
@ -116,6 +120,9 @@ export class ApiV3Service {
// /api/v3/news
public readonly news = this.apiV3CustomEndpoint(ApiV3NewsPaths);
// /api/v3/storages
public readonly storages = this.apiV3CustomEndpoint(Apiv3StoragesPaths);
// /api/v3/types
public readonly types = this.apiV3CustomEndpoint(ApiV3TypesPaths);

@ -0,0 +1,45 @@
// -- 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.
//++
import {
ApiV3GettableResource,
ApiV3ResourceCollection,
} from 'core-app/core/apiv3/paths/apiv3-resource';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { IStorage } from 'core-app/core/state/storages/storage.model';
export class Apiv3StoragesPaths
extends ApiV3ResourceCollection<IStorage, ApiV3GettableResource<IStorage>> {
// /api/v3/storages/files
public readonly files = this.subResource('files');
constructor(protected apiRoot:ApiV3Service,
protected basePath:string) {
super(apiRoot, basePath, 'storages');
}
}

@ -44,13 +44,9 @@ export class ConfigurationService {
public constructor(
readonly I18n:I18nService,
readonly apiV3Service:ApiV3Service,
readonly weekdayService:WeekdayService,
) {
this.initialized = Promise
.all([
this.loadConfiguration(),
this.weekdayService.loadWeekdays().toPromise(),
])
this.initialized = this
.loadConfiguration()
.then(() => true)
.catch(() => false);
}

@ -92,7 +92,7 @@ export class CurrentUserService {
return { filters, pageSize: -1 };
}),
switchMap((params) => this.capabilitiesService.require$(params)),
switchMap((params) => this.capabilitiesService.require(params)),
);
}

@ -59,7 +59,7 @@ export class WeekdayService {
*/
public isNonWorkingDay(date:Date|number):boolean {
const isoDayOfWeek = (typeof date === 'number') ? date : moment(date).isoWeekday();
return !!this.weekdays.find((wd) => wd.day === isoDayOfWeek && !wd.working);
return !!(this.weekdays || []).find((wd) => wd.day === isoDayOfWeek && !wd.working);
}
loadWeekdays():Observable<IWeekday[]> {

@ -61,20 +61,19 @@ import {
CollectionStore,
ResourceCollectionService,
} from 'core-app/core/state/resource-collection.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
@Injectable()
export class AttachmentsResourceService extends ResourceCollectionService<IAttachment> {
constructor(
private readonly I18n:I18nService,
private readonly http:HttpClient,
private readonly apiV3Service:ApiV3Service,
private readonly fileUploadService:OpenProjectFileUploadService,
private readonly directFileUploadService:OpenProjectDirectFileUploadService,
private readonly configurationService:ConfigurationService,
private readonly toastService:ToastService,
) {
super();
}
@InjectField() I18n:I18nService;
@InjectField() fileUploadService:OpenProjectFileUploadService;
@InjectField() directFileUploadService:OpenProjectDirectFileUploadService;
@InjectField() configurationService:ConfigurationService;
@InjectField() toastService:ToastService;
/**
* This method ensures that a specific collection is fetched, if not available.
@ -246,4 +245,8 @@ export class AttachmentsResourceService extends ResourceCollectionService<IAttac
protected createStore():CollectionStore<IAttachment> {
return new AttachmentsStore();
}
protected basePath():string {
return this.apiV3Service.attachments.path;
}
}

@ -220,7 +220,7 @@ describe('Capabilities service', () => {
};
service
.require$(params)
.require(params)
.subscribe((caps) => {
expect(caps.length).toEqual(1);
});
@ -234,7 +234,7 @@ describe('Capabilities service', () => {
};
service
.require$(params)
.require(params)
.subscribe((caps) => {
expect(caps.length).toEqual(1);
});
@ -244,7 +244,7 @@ describe('Capabilities service', () => {
};
service
.require$(params)
.require(params)
.subscribe((caps) => {
expect(caps.length).toEqual(2);
});
@ -254,7 +254,7 @@ describe('Capabilities service', () => {
};
service
.require$(params)
.require(params)
.subscribe((caps) => {
expect(caps.length).toEqual(1);
});

@ -5,8 +5,6 @@ import {
switchMap,
} from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
@ -17,23 +15,11 @@ import {
CollectionStore,
ResourceCollectionService,
} from 'core-app/core/state/resource-collection.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
@Injectable()
export class CapabilitiesResourceService extends ResourceCollectionService<ICapability> {
constructor(
private http:HttpClient,
private apiV3Service:ApiV3Service,
private toastService:ToastService,
) {
super();
}
private get capabilitiesPath():string {
return this
.apiV3Service
.capabilities
.path;
}
@InjectField() toastService:ToastService;
/**
* Require the available capabilities for the given filter params
@ -69,7 +55,7 @@ export class CapabilitiesResourceService extends ResourceCollectionService<ICapa
public fetchCapabilities(params:ApiV3ListParameters):Observable<IHALCollection<ICapability>> {
return this
.fetchCollection(this.http, this.capabilitiesPath, params)
.fetchCollection(params)
.pipe(
catchError((error) => {
this.toastService.addError(error);
@ -81,4 +67,11 @@ export class CapabilitiesResourceService extends ResourceCollectionService<ICapa
protected createStore():CollectionStore<ICapability> {
return new CapabilitiesStore();
}
protected basePath():string {
return this
.apiV3Service
.capabilities
.path;
}
}

@ -11,6 +11,7 @@ import {
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { IHalResourceLinks } from 'core-app/core/state/hal-resource';
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
import { filter } from 'lodash';
export interface CollectionResponse {
ids:ID[];
@ -63,18 +64,34 @@ export function collectionKey(params:ApiV3ListParameters):string {
export function setCollectionLoading<T extends { id:ID }>(
store:EntityStore<CollectionState<T>>,
collectionUrl:string,
loading:boolean,
):void {
store.update(({ loadingCollections }) => (
{
loadingCollections: {
...loadingCollections,
[collectionUrl]: loading,
[collectionUrl]: true,
},
}
));
}
/**
* Mark a collection key as no longer loading
*
* @param store An entity store for the collection
* @param collectionUrl The key to insert the collection at
*/
export function removeCollectionLoading<T extends { id:ID }>(
store:EntityStore<CollectionState<T>>,
collectionUrl:string,
):void {
store.update(({ loadingCollections }) => (
{
loadingCollections: filter(loadingCollections, (_, key) => key !== collectionUrl),
}
));
}
/**
* Insert a collection into the given entity store
*

@ -1,17 +1,21 @@
import { Injectable } from '@angular/core';
import {
finalize,
map,
tap,
} from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { HttpClient } from '@angular/common/http';
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import {
ApiV3ListFilter,
ApiV3ListParameters,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import {
collectionKey,
extendCollectionElementsWithId,
insertCollectionIntoState,
removeCollectionLoading,
setCollectionLoading,
} from 'core-app/core/state/collection-store';
import { DayStore } from 'core-app/core/state/days/day.store';
import { IDay } from 'core-app/core/state/days/day.model';
@ -22,29 +26,47 @@ import {
@Injectable()
export class DayResourceService extends ResourceCollectionService<IDay> {
private get daysPath():string {
protected basePath():string {
return this
.apiV3Service
.days
.path;
}
constructor(
private http:HttpClient,
private apiV3Service:ApiV3Service,
) {
super();
isNonWorkingDay$(input:Date):Observable<boolean> {
const date = moment(input).format('YYYY-MM-DD');
return this
.requireNonWorkingYear$(input)
.pipe(
map((days) => days.findIndex((day:IDay) => !day.working && day.date === date) !== -1),
);
}
requireNonWorkingYear$(date:Date):Observable<IDay[]> {
const from = moment(date).startOf('year').format('YYYY-MM-DD');
const to = moment(date).endOf('year').format('YYYY-MM-DD');
const filters:ApiV3ListFilter[] = [
['date', '<>d', [from, to]],
['working', '=', ['f']],
];
return this.require({ filters });
}
fetchDays(params:ApiV3ListParameters):Observable<IHALCollection<IDay>> {
fetchCollection(params:ApiV3ListParameters):Observable<IHALCollection<IDay>> {
const collectionURL = collectionKey(params);
setCollectionLoading(this.store, collectionURL);
return this
.http
.get<IHALCollection<IDay>>(this.daysPath + collectionURL)
.get<IHALCollection<IDay>>(this.basePath() + collectionURL)
.pipe(
map((collection) => extendCollectionElementsWithId(collection)),
tap((collection) => insertCollectionIntoState(this.store, collection, collectionURL)),
finalize(() => removeCollectionLoading(this.store, collectionURL)),
);
}

@ -8,9 +8,7 @@ import {
EMPTY,
Observable,
} from 'rxjs';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { HttpClient } from '@angular/common/http';
import {
extendCollectionElementsWithId,
insertCollectionIntoState,
@ -24,21 +22,6 @@ import {
@Injectable()
export class WeekdayResourceService extends ResourceCollectionService<IWeekday> {
private get weekdaysPath():string {
return this
.apiV3Service
.days
.week
.path;
}
constructor(
private http:HttpClient,
private apiV3Service:ApiV3Service,
) {
super();
}
require():Observable<IWeekday[]> {
return this
.query
@ -49,12 +32,12 @@ export class WeekdayResourceService extends ResourceCollectionService<IWeekday>
);
}
private fetchWeekdays():Observable<IHALCollection<IWeekday>> {
protected fetchWeekdays():Observable<IHALCollection<IWeekday>> {
const collectionURL = 'all'; // We load all weekdays
return this
.http
.get<IHALCollection<IWeekday>>(this.weekdaysPath)
.get<IHALCollection<IWeekday>>(this.basePath())
.pipe(
map((collection) => extendCollectionElementsWithId(collection)),
tap((collection) => insertCollectionIntoState(this.store, collection, collectionURL)),
@ -64,4 +47,12 @@ export class WeekdayResourceService extends ResourceCollectionService<IWeekday>
protected createStore():CollectionStore<IWeekday> {
return new WeekdayStore();
}
protected basePath():string {
return this
.apiV3Service
.days
.week
.path;
}
}

@ -28,7 +28,7 @@
import { applyTransaction } from '@datorama/akita';
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';
import { from } from 'rxjs';
import {
catchError,
@ -47,16 +47,12 @@ import { insertCollectionIntoState, removeEntityFromCollectionAndState } from 'c
import { CollectionStore, ResourceCollectionService } from 'core-app/core/state/resource-collection.service';
import { IHalResourceLink } from 'core-app/core/state/hal-resource';
import { IStorageFile } from 'core-app/core/state/storage-files/storage-file.model';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
@Injectable()
export class FileLinksResourceService extends ResourceCollectionService<IFileLink> {
constructor(
private readonly http:HttpClient,
private readonly toastService:ToastService,
) {
super();
}
@InjectField() toastService:ToastService;
updateCollectionsForWorkPackage(fileLinksSelfLink:string):void {
this.http
@ -151,4 +147,8 @@ export class FileLinksResourceService extends ResourceCollectionService<IFileLin
)
.subscribe();
}
protected basePath():string {
return this.apiV3Service.file_links.path;
}
}

@ -2,15 +2,6 @@ import { Injectable } from '@angular/core';
import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ID } from '@datorama/akita';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { HttpClient } from '@angular/common/http';
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import {
collectionKey,
insertCollectionIntoState,
} from 'core-app/core/state/collection-store';
import {
markNotificationsAsRead,
notificationsMarkedRead,
@ -26,36 +17,12 @@ import {
CollectionStore,
ResourceCollectionService,
} from 'core-app/core/state/resource-collection.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
@EffectHandler
@Injectable()
export class InAppNotificationsResourceService extends ResourceCollectionService<INotification> {
private get notificationsPath():string {
return this
.apiV3Service
.notifications
.path;
}
constructor(
readonly actions$:ActionsService,
private http:HttpClient,
private apiV3Service:ApiV3Service,
private toastService:ToastService,
) {
super();
}
fetchNotifications(params:ApiV3ListParameters):Observable<IHALCollection<INotification>> {
const collectionURL = collectionKey(params);
return this
.http
.get<IHALCollection<INotification>>(this.notificationsPath + collectionURL)
.pipe(
tap((collection) => insertCollectionIntoState(this.store, collection, collectionURL)),
);
}
@InjectField() actions$:ActionsService;
update(id:ID, inAppNotification:Partial<INotification>):void {
this.store.update(id, inAppNotification);
@ -88,4 +55,11 @@ export class InAppNotificationsResourceService extends ResourceCollectionService
protected createStore():CollectionStore<INotification> {
return new InAppNotificationsStore();
}
protected basePath():string {
return this
.apiV3Service
.notifications
.path;
}
}

@ -5,10 +5,8 @@ import {
} from 'rxjs/operators';
import { Observable } from 'rxjs';
import { applyTransaction } from '@datorama/akita';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { HttpClient } from '@angular/common/http';
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import {
collectionKey,
@ -23,25 +21,14 @@ import {
CollectionStore,
ResourceCollectionService,
} from 'core-app/core/state/resource-collection.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
@EffectHandler
@Injectable()
export class PrincipalsResourceService extends ResourceCollectionService<IPrincipal> {
private get principalsPath():string {
return this
.apiV3Service
.principals
.path;
}
@InjectField() actions$:ActionsService;
constructor(
readonly actions$:ActionsService,
private http:HttpClient,
private apiV3Service:ApiV3Service,
private toastService:ToastService,
) {
super();
}
@InjectField() toastService:ToastService;
fetchUser(id:string|number):Observable<IUser> {
return this.http
@ -64,7 +51,7 @@ export class PrincipalsResourceService extends ResourceCollectionService<IPrinci
return this
.http
.get<IHALCollection<IPrincipal>>(this.principalsPath + collectionURL)
.get<IHALCollection<IPrincipal>>(this.basePath() + collectionURL)
.pipe(
tap((collection) => insertCollectionIntoState(this.store, collection, collectionURL)),
catchError((error) => {
@ -77,4 +64,11 @@ export class PrincipalsResourceService extends ResourceCollectionService<IPrinci
protected createStore():CollectionStore<IPrincipal> {
return new PrincipalsStore();
}
protected basePath():string {
return this
.apiV3Service
.principals
.path;
}
}

@ -27,20 +27,8 @@
//++
import { Injectable } from '@angular/core';
import {
catchError,
tap,
} from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import {
collectionKey,
insertCollectionIntoState,
} from 'core-app/core/state/collection-store';
import { IProject } from './project.model';
import {
CollectionStore,
@ -50,36 +38,6 @@ import { ProjectsStore } from 'core-app/core/state/projects/projects.store';
@Injectable()
export class ProjectsResourceService extends ResourceCollectionService<IProject> {
private get projectsPath():string {
return this
.apiV3Service
.projects
.path;
}
constructor(
private http:HttpClient,
private apiV3Service:ApiV3Service,
private toastService:ToastService,
) {
super();
}
fetchProjects(params:ApiV3ListParameters):Observable<IHALCollection<IProject>> {
const collectionURL = collectionKey(params);
return this
.http
.get<IHALCollection<IProject>>(this.projectsPath + collectionURL)
.pipe(
tap((collection) => insertCollectionIntoState(this.store, collection, collectionURL)),
catchError((error) => {
this.toastService.addError(error);
throw error;
}),
);
}
update(link:string):Observable<IProject> {
return this.http.get<IProject>(link)
.pipe(
@ -92,4 +50,11 @@ export class ProjectsResourceService extends ResourceCollectionService<IProject>
protected createStore():CollectionStore<IProject> {
return new ProjectsStore();
}
protected basePath():string {
return this
.apiV3Service
.projects
.path;
}
}

@ -33,6 +33,7 @@ import {
} from '@datorama/akita';
import { Observable } from 'rxjs';
import {
catchError,
filter,
finalize,
map,
@ -44,21 +45,60 @@ import {
CollectionResponse,
CollectionState,
insertCollectionIntoState,
removeCollectionLoading,
setCollectionLoading,
} from 'core-app/core/state/collection-store';
import { omit } from 'lodash';
import isDefinedEntity from 'core-app/core/state/is-defined-entity';
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { HttpClient } from '@angular/common/http';
import {
HttpClient,
HttpErrorResponse,
} from '@angular/common/http';
import {
Injectable,
Injector,
} from '@angular/core';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
export type CollectionStore<T> = EntityStore<CollectionState<T>>;
@Injectable()
export abstract class ResourceCollectionService<T extends { id:ID }> {
protected store:CollectionStore<T> = this.createStore();
protected query = new QueryEntity(this.store);
constructor(
readonly injector:Injector,
readonly http:HttpClient,
readonly apiV3Service:ApiV3Service,
readonly toastService:ToastService,
) {
}
/**
* Require the results for the given filter params
* Returns a cached set if it was loaded already.
*
* @param params List params to require
* @private
*/
public require(params:ApiV3ListParameters):Observable<T[]> {
const key = collectionKey(params);
if (this.collectionExists(key) || this.collectionLoading(key)) {
return this.loadedCollection(key);
}
return this
.fetchCollection(params)
.pipe(
switchMap(() => this.loadedCollection(key)),
);
}
/**
* Retrieve a collection from the store
*
@ -173,24 +213,50 @@ export abstract class ResourceCollectionService<T extends { id:ID }> {
}
/**
* Create a new instance of this resource service's underyling store.
* @protected
* Fetch a given collection, returning only its results
*/
protected abstract createStore():CollectionStore<T>;
fetchResults(params:ApiV3ListParameters|string):Observable<T[]> {
return this
.fetchCollection(params)
.pipe(
map((collection) => collection._embedded.elements),
);
}
/**
* Fetch a given collection, ensuring it is being flagged as loaded
*/
protected fetchCollection(http:HttpClient, basePath:string, params:ApiV3ListParameters):Observable<IHALCollection<T>> {
const key = collectionKey(params);
fetchCollection(params:ApiV3ListParameters|string):Observable<IHALCollection<T>> {
const key = typeof params === 'string' ? params : collectionKey(params);
setCollectionLoading(this.store, key, true);
setCollectionLoading(this.store, key);
return http
.get<IHALCollection<T>>(basePath + key)
return this
.http
.get<IHALCollection<T>>(this.basePath() + key)
.pipe(
tap((collection) => insertCollectionIntoState(this.store, collection, key)),
finalize(() => setCollectionLoading(this.store, key, false)),
finalize(() => removeCollectionLoading(this.store, key)),
catchError((error:unknown) => {
this.handleCollectionLoadingError(error as HttpErrorResponse, key);
throw error;
}),
);
}
/**
* Create a new instance of this resource service's underyling store.
* @protected
*/
protected abstract createStore():CollectionStore<T>;
/**
* Base path for this collection
* @protected
*/
protected abstract basePath():string;
protected handleCollectionLoadingError(error:HttpErrorResponse, _collectionKey:string):void {
this.toastService.addError(error);
}
}

@ -43,9 +43,6 @@ import { insertCollectionIntoState } from 'core-app/core/state/collection-store'
@Injectable()
export class StorageFilesResourceService extends ResourceCollectionService<IStorageFile> {
constructor(private readonly http:HttpClient) {
super();
}
protected createStore():CollectionStore<IStorageFile> {
return new StorageFilesStore();
@ -69,4 +66,8 @@ export class StorageFilesResourceService extends ResourceCollectionService<IStor
reset():void {
this.store.reset();
}
protected basePath():string {
return this.apiV3Service.storages.files.path;
}
}

@ -27,7 +27,6 @@
//++
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { forkJoin } from 'rxjs';
import { IStorage } from 'core-app/core/state/storages/storage.model';
import { StoragesStore } from 'core-app/core/state/storages/storages.store';
@ -41,10 +40,6 @@ import {
@Injectable()
export class StoragesResourceService extends ResourceCollectionService<IStorage> {
constructor(private readonly http:HttpClient) {
super();
}
updateCollection(key:string, storageLinks:IHalResourceLink[]):void {
forkJoin(
storageLinks.map((link) => this.http.get<IStorage>(link.href)),
@ -57,4 +52,8 @@ export class StoragesResourceService extends ResourceCollectionService<IStorage>
protected createStore():CollectionStore<IStorage> {
return new StoragesStore();
}
protected basePath():string {
return this.apiV3Service.storages.path;
}
}

@ -1,70 +1,27 @@
import { Injectable } from '@angular/core';
import {
catchError,
tap,
} from 'rxjs/operators';
import { Observable } from 'rxjs';
import {
applyTransaction,
ID,
} from '@datorama/akita';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { HttpClient } from '@angular/common/http';
import {
collectionKey,
insertCollectionIntoState,
} from 'core-app/core/state/collection-store';
import {
EffectHandler,
} from 'core-app/core/state/effects/effect-handler.decorator';
import { EffectHandler } from 'core-app/core/state/effects/effect-handler.decorator';
import { ActionsService } from 'core-app/core/state/actions/actions.service';
import { ViewsStore } from 'core-app/core/state/views/views.store';
import { ViewsQuery } from 'core-app/core/state/views/views.query';
import { IView } from 'core-app/core/state/views/view.model';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { addParamToHref } from 'core-app/shared/helpers/url-helpers';
import {
CollectionStore,
ResourceCollectionService,
} from 'core-app/core/state/resource-collection.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
@EffectHandler
@Injectable()
export class ViewsResourceService extends ResourceCollectionService<IView> {
private get viewsPath():string {
return this
.apiV3Service
.views
.path;
}
@InjectField() actions$:ActionsService;
constructor(
readonly actions$:ActionsService,
private http:HttpClient,
private apiV3Service:ApiV3Service,
private toastService:ToastService,
) {
super();
protected createStore():CollectionStore<IView> {
return new ViewsStore();
}
fetchViews(params:ApiV3ListParameters):Observable<IHALCollection<IView>> {
const collectionURL = collectionKey(params);
protected basePath():string {
return this
.http
.get<IHALCollection<IView>>(addParamToHref(this.viewsPath + collectionURL, { pageSize: '-1' }))
.pipe(
tap((collection) => insertCollectionIntoState(this.store, collection, collectionURL)),
catchError((error) => {
this.toastService.addError(error);
throw error;
}),
);
}
protected createStore():CollectionStore<IView> {
return new ViewsStore();
.apiV3Service
.views
.path;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save