Merge remote-tracking branch 'origin/dev' into bim/feature/viewpoints

pull/7890/head
Oliver Günther 5 years ago
commit bdf3909fdf
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 10
      app/assets/javascripts/custom-fields.js
  2. 4
      app/assets/stylesheets/openproject/_generic.sass
  3. 32
      app/contracts/work_packages/base_contract.rb
  4. 2
      app/contracts/work_packages/create_contract.rb
  5. 1
      app/models/permitted_params.rb
  6. 31
      app/models/priority/inexistent_priority.rb
  7. 31
      app/models/status/inexistent_status.rb
  8. 32
      app/models/type/inexistent_type.rb
  9. 31
      app/models/user/inexistent_user.rb
  10. 6
      app/services/work_packages/create_service.rb
  11. 2
      app/services/work_packages/set_attributes_service.rb
  12. 1
      app/views/custom_fields/_form.html.erb
  13. 56
      config/locales/crowdin/cs.yml
  14. 4
      config/locales/crowdin/fr.yml
  15. 20
      config/locales/crowdin/js-cs.yml
  16. 6
      config/locales/crowdin/tr.yml
  17. 3
      config/locales/en.yml
  18. 5
      db/migrate/20191115141154_add_content_orientation_to_custom_fields.rb
  19. 15
      frontend/src/app/components/wp-card-view/event-handler/click-handler.ts
  20. 11
      frontend/src/app/components/wp-card-view/wp-card-view.component.ts
  21. 2
      frontend/src/app/components/wp-card-view/wp-single-card/wp-single-card.component.html
  22. 4
      frontend/src/app/components/wp-card-view/wp-single-card/wp-single-card.component.sass
  23. 2
      frontend/src/app/components/wp-edit-form/display-field-renderer.ts
  24. 11
      frontend/src/app/modules/common/ckeditor/ckeditor-setup.service.ts
  25. 2
      frontend/src/app/modules/fields/display/display-field.module.ts
  26. 7
      frontend/src/app/modules/fields/display/field-types/formattable-display-field.module.ts
  27. 3
      frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts
  28. 14
      frontend/src/app/modules/fields/edit/field-types/multi-select-edit-field.component.ts
  29. 16
      frontend/src/app/modules/fields/edit/field-types/select-edit-field.component.ts
  30. 5
      frontend/src/app/modules/fields/field.base.ts
  31. 2
      frontend/src/app/modules/work_packages/routing/wp-list/wp-list.component.ts
  32. 2
      frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts
  33. 2
      lib/api/decorators/allowed_values_by_collection_representer.rb
  34. 13
      lib/api/decorators/property_schema_representer.rb
  35. 22
      lib/api/decorators/schema_representer.rb
  36. 2
      lib/api/errors/error_base.rb
  37. 4
      lib/api/errors/unwritable_property.rb
  38. 12
      lib/api/utilities/endpoints/modify.rb
  39. 9
      lib/api/v3/memberships/schemas/membership_schema_representer.rb
  40. 20
      lib/api/v3/projects/schemas/project_schema_representer.rb
  41. 1
      lib/api/v3/queries/schemas/custom_option_filter_dependency_representer.rb
  42. 1
      lib/api/v3/queries/schemas/filter_dependency_representer.rb
  43. 5
      lib/api/v3/queries/schemas/query_filter_instance_schema_representer.rb
  44. 56
      lib/api/v3/queries/schemas/query_schema_representer.rb
  45. 9
      lib/api/v3/utilities/custom_field_injector.rb
  46. 22
      lib/api/v3/versions/schemas/version_schema_representer.rb
  47. 1
      modules/backlogs/lib/api/v3/queries/schemas/backlogs_type_dependency_representer.rb
  48. 43
      modules/bcf/app/contracts/bcf/issues/base_contract.rb
  49. 39
      modules/bcf/app/contracts/bcf/issues/create_contract.rb
  50. 50
      modules/bcf/app/controllers/bcf/api/v2_1/endpoints/create.rb
  51. 46
      modules/bcf/app/controllers/bcf/api/v2_1/endpoints/modify_mixin.rb
  52. 15
      modules/bcf/app/controllers/bcf/api/v2_1/endpoints/update.rb
  53. 41
      modules/bcf/app/controllers/bcf/api/v2_1/project_extensions/api.rb
  54. 1
      modules/bcf/app/controllers/bcf/api/v2_1/projects_api.rb
  55. 19
      modules/bcf/app/controllers/bcf/api/v2_1/topics_api.rb
  56. 6
      modules/bcf/app/controllers/bcf/issues_controller.rb
  57. 126
      modules/bcf/app/models/bcf/issue.rb
  58. 64
      modules/bcf/app/representers/bcf/api/v2_1/errors/error_mapper.rb
  59. 4
      modules/bcf/app/representers/bcf/api/v2_1/errors/error_representer.rb
  60. 110
      modules/bcf/app/representers/bcf/api/v2_1/project_extensions/definitions.rb
  61. 44
      modules/bcf/app/representers/bcf/api/v2_1/project_extensions/representer.rb
  62. 113
      modules/bcf/app/representers/bcf/api/v2_1/topics/single_representer.rb
  63. 51
      modules/bcf/app/services/bcf/issues/create_service.rb
  64. 34
      modules/bcf/app/services/bcf/issues/set_attributes_service.rb
  65. 162
      modules/bcf/app/services/bcf/issues/transform_attributes_service.rb
  66. 4
      modules/bcf/config/locales/crowdin/tr.yml
  67. 9
      modules/bcf/db/migrate/20191119144123_add_issue_columns.rb
  68. 3
      modules/bcf/lib/open_project/bcf/bcf_xml/importer.rb
  69. 186
      modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
  70. 2
      modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
  71. 85
      modules/bcf/spec/api/v3/work_packages/work_package_representer_spec.rb
  72. 2
      modules/bcf/spec/bcf/bcf_xml/importer_spec.rb
  73. 13
      modules/bcf/spec/bcf/bcf_xml/issue_reader_spec.rb
  74. 6
      modules/bcf/spec/bcf/bcf_xml/issue_writer_spec.rb
  75. 45
      modules/bcf/spec/contracts/bcf/issues/create_contract_spec.rb
  76. 129
      modules/bcf/spec/contracts/bcf/issues/shared_contract_examples.rb
  77. 28
      modules/bcf/spec/factories/bcf_issue_factory.rb
  78. 27
      modules/bcf/spec/models/bcf/issue_spec.rb
  79. 135
      modules/bcf/spec/representers/bcf/api/v2_1/project_extensions/definitions_spec.rb
  80. 53
      modules/bcf/spec/representers/bcf/api/v2_1/topics/single_representer_rendering_spec.rb
  81. 116
      modules/bcf/spec/requests/api/bcf/v2_1/project_extensions_api_spec.rb
  82. 2
      modules/bcf/spec/requests/api/bcf/v2_1/projects_api_spec.rb
  83. 83
      modules/bcf/spec/requests/api/bcf/v2_1/shared_responses.rb
  84. 312
      modules/bcf/spec/requests/api/bcf/v2_1/topics_api_spec.rb
  85. 173
      modules/bcf/spec/services/bcf/issues/create_service_spec.rb
  86. 16
      modules/boards/spec/requests/api/v3/grids/grids_resource_spec.rb
  87. 2
      modules/boards/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb
  88. 4
      modules/dashboards/config/locales/crowdin/tr.yml
  89. 23
      modules/grids/app/representers/api/v3/grids/schemas/grid_schema_representer.rb
  90. 10
      modules/grids/spec/lib/api/v3/grids/schemas/grid_schema_representer_spec.rb
  91. 16
      modules/my_page/spec/requests/api/v3/grids/grids_resource_spec.rb
  92. 2
      modules/my_page/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb
  93. 56
      spec/contracts/work_packages/base_contract_spec.rb
  94. 49
      spec/features/work_packages/display_representations/switch_display_representations_spec.rb
  95. 4
      spec/lib/api/v3/memberships/schemas/membership_schema_representer_spec.rb
  96. 16
      spec/lib/api/v3/projects/schemas/project_schema_representer_spec.rb
  97. 6
      spec/lib/api/v3/queries/schemas/query_filter_instance_schema_representer_spec.rb
  98. 40
      spec/lib/api/v3/queries/schemas/query_schema_representer_spec.rb
  99. 6
      spec/lib/api/v3/support/api_v3_filter_dependency.rb
  100. 7
      spec/lib/api/v3/support/schema_examples.rb
  101. Some files were not shown because too many files have changed in this diff Show More

@ -44,7 +44,8 @@
possibleValues = $('#custom_field_possible_values_attributes'),
defaultValueFields = $('#custom_field_default_value_attributes'),
spanDefaultText = $('#default_value_text'),
spanDefaultBool = $('#default_value_bool');
spanDefaultBool = $('#default_value_bool'),
textOrientationField = $('#custom_field_text_orientation');
var deactivate = function(element) {
element.hide().find('input, textarea').not('.destroy_flag,.-cf-ignore-disabled').attr('disabled', true);
@ -65,7 +66,7 @@
unsearchable = function() { searchable.attr('checked', false).hide(); };
// defaults (reset these fields before doing anything else)
$.each([spanDefaultBool, spanDefaultText, multiSelect], function(idx, element) {
$.each([spanDefaultBool, spanDefaultText, multiSelect, textOrientationField], function(idx, element) {
deactivate(element);
});
show(defaultValueFields);
@ -107,6 +108,11 @@
hide(lengthField, regexpField, defaultValueFields);
unsearchable();
break;
case 'text':
show(lengthField, regexpField, searchable, textOrientationField);
deactivate(possibleValues);
activate(textOrientationField);
break;
default:
show(lengthField, regexpField, searchable);
deactivate(possibleValues);

@ -86,6 +86,10 @@
font-style: italic
.-small-font
font-size: 12px
.-rtl
direction: rtl
.-placeholder &
direction: ltr
.drop-zone.-dragged-over
background-color: #eaeaea60

@ -103,7 +103,9 @@ module WorkPackages
message: :greater_than_or_equal_to_start_date,
allow_blank: true },
unless: Proc.new { |wp| wp.start_date.blank? }
validate :validate_enabled_type
validate :validate_type_exists
validate :validate_milestone_constraint
validate :validate_parent_not_milestone
@ -112,13 +114,17 @@ module WorkPackages
validate :validate_parent_in_same_project
validate :validate_parent_not_subtask
validate :validate_status_exists
validate :validate_status_transition
validate :validate_active_priority
validate :validate_priority_exists
validate :validate_category
validate :validate_estimated_hours
validate :validate_assigned_to_exists
def initialize(work_package, user, options: {})
super
@ -149,11 +155,19 @@ module WorkPackages
def validate_enabled_type
# Checks that the issue can not be added/moved to a disabled type
if model.project && (model.type_id_changed? || model.project_id_changed?)
if type_context_changed?
errors.add :type_id, :inclusion unless model.project.types.include?(model.type)
end
end
def validate_assigned_to_exists
errors.add :assigned_to, :does_not_exist if model.assigned_to&.is_a?(User::InexistentUser)
end
def validate_type_exists
errors.add :type, :does_not_exist if type_inexistent?
end
def validate_milestone_constraint
if model.is_milestone? && model.due_date && model.start_date && model.start_date != model.due_date
errors.add :due_date, :not_start_date
@ -186,6 +200,10 @@ module WorkPackages
end
end
def validate_status_exists
errors.add :status, :does_not_exist if model.status&.is_a?(Status::InexistentStatus)
end
def validate_status_transition
if status_changed? && status_exists? && !(model.type_id_changed? || status_transition_exists?)
errors.add :status_id, :status_transition_invalid
@ -198,6 +216,10 @@ module WorkPackages
end
end
def validate_priority_exists
errors.add :priority, :does_not_exist if model.priority&.is_a?(Priority::InexistentPriority)
end
def validate_category
if inexistent_category?
errors.add :category, :does_not_exist
@ -287,5 +309,13 @@ module WorkPackages
query
end
end
def type_context_changed?
model.project && !type_inexistent? && (model.type_id_changed? || model.project_id_changed?)
end
def type_inexistent?
model.type.is_a?(Type::InexistentType)
end
end
end

@ -32,8 +32,6 @@ require 'work_packages/base_contract'
module WorkPackages
class CreateContract < BaseContract
# TODO: Think about whether this can be removed
# as it is unwriteable. So why bother checking for the correct author
attribute :author_id,
writeable: false do
errors.add :author_id, :invalid if model.author != user

@ -480,6 +480,7 @@ class PermittedParams
:default_value,
:possible_values,
:multi_value,
:content_right_to_left,
{ custom_options_attributes: %i(id value default_value position) },
type_ids: []
],

@ -0,0 +1,31 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class Priority::InexistentPriority < IssuePriority; end

@ -0,0 +1,31 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class Status::InexistentStatus < Status; end

@ -0,0 +1,32 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class Type::InexistentType < Type
end

@ -0,0 +1,31 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class User::InexistentUser < User; end

@ -74,7 +74,7 @@ class WorkPackages::CreateService
end
def set_attributes(attributes, wp)
WorkPackages::SetAttributesService
attributes_service_class
.new(user: user,
model: wp,
contract_class: contract_class)
@ -96,4 +96,8 @@ class WorkPackages::CreateService
result
end
def attributes_service_class
::WorkPackages::SetAttributesService
end
end

@ -177,7 +177,7 @@ class WorkPackages::SetAttributesService < ::BaseServices::SetAttributes
end
def reassign_status(available_statuses)
return if available_statuses.include? work_package.status
return if available_statuses.include?(work_package.status) || work_package.status.is_a?(Status::InexistentStatus)
new_status = available_statuses.detect(&:is_default) || available_statuses.first
work_package.status = new_status if new_status.present?

@ -107,6 +107,7 @@ See docs/COPYRIGHT.rdoc for more details.
<div class="form--field"><%= f.check_box :is_for_all %></div>
<div class="form--field"><%= f.check_box :is_filter %></div>
<div class="form--field" id="searchable_container"><%= f.check_box :searchable %></div>
<div class="form--field" id="custom_field_text_orientation"><%= f.check_box :content_right_to_left %></div>
<% when "UserCustomField" %>
<div class="form--field"><%= f.check_box :is_required %></div>
<div class="form--field"><%= f.check_box :visible %></div>

@ -30,33 +30,33 @@ cs:
custom_styles:
custom_colors: "Vlastní barvy"
customize: "Přizpůsobte si svou OpenProject instalaci vlastním logem. Poznámka: Logo bude veřejně přístupné."
enterprise_notice: "As a special 'Thank you!' for their financial contribution to develop OpenProject, this tiny feature is only available for Enterprise Edition support subscribers."
manage_colors: "Edit color select options"
enterprise_notice: "Jako zvláštní 'Děkujeme!' za jejich finanční příspěvky na vývoj OpenProjektu, je tato malá funkce dostupná pouze pro podporu Enterprise Edition."
manage_colors: "Upravit možnosti výběru barvy"
instructions:
alternative_color: "Strong accent color, typically used for most the important button on a screen."
alternative_color: "Silný odstín, obvykle používaný pro nejdůležitější tlačítko na obrazovce."
content_link_color: "Barva písma většiny odkazů."
primary_color: "Hlavní barva."
primary_color_dark: "Typically a darker version of the main color used for hover effects."
primary_color_dark: "Obvykle tmavší verze hlavní barvy použité pro efekt vznášení."
header_bg_color: "Barva pozadí záhlaví."
header_item_bg_hover_color: "Barva pozadí klikacích položek záhlaví při najetí myší."
header_item_font_color: "Barva pozadí klikacích položek záhlaví."
header_item_font_hover_color: "Font color of clickable header items when hovered with the mouse."
header_border_bottom_color: "Thin line under the header. Leave this field empty if you don't want any line."
main_menu_bg_color: "Left side menu's background color."
header_item_font_hover_color: "Barva pozadí klikacích položek záhlaví při najetí myší."
header_border_bottom_color: "Tenký řádek pod záhlavím. Ponechte toto pole prázdné, pokud nechcete žádný řádek."
main_menu_bg_color: "Barva pozadí levého menu."
enterprise:
upgrade_to_ee: "Upgrade to Enterprise Edition"
add_token: "Upload an Enterprise Edition support token"
replace_token: "Replace your current support token"
order: "Order Enterprise Edition"
paste: "Paste your Enterprise Edition support token"
required_for_feature: "This feature is only available with an active Enterprise Edition support token."
enterprise_link: "For more information, click here."
upgrade_to_ee: "Upgradovat na Enterprise Edition"
add_token: "Nahrát podpůrný token Enterprise Edition"
replace_token: "Nahradit aktuální podpůrný token"
order: "Objednávka Enterprise edice"
paste: "Vložte svůj podpůrný token Enterprise Edition"
required_for_feature: "Tato funkce je dostupná pouze s aktivním podpůrným tokenem pro Enterprise Edition."
enterprise_link: "Pro více informací klikněte zde."
announcements:
show_until: Ukázat až do
is_active: aktuálně zobrazené
is_inactive: v současné době nejsou zobrazeny
attribute_help_texts:
text_overview: 'In this view, you can create custom help texts for attributes view. When defined, these texts can be shown by clicking the help icon next to its belonging attribute.'
text_overview: 'V tomto zobrazení můžete vytvořit vlastní nápovědné texty pro zobrazení atributů. Pokud je definováno, mohou být tyto texty zobrazeny klepnutím na ikonu nápovědy vedle jejího atributu.'
label_plural: 'Texty nápovědy atributu'
show_preview: 'Náhled textu'
add_new: 'Přidat text nápovědy'
@ -66,16 +66,16 @@ cs:
no_results_content_title: Nyní neexistují žádné ověřovací režimy.
no_results_content_text: Vytvořit nový režim ověřování
ldap_auth_sources:
connection_encryption: 'Connection encryption'
connection_encryption: 'Šifrování připojení'
tls_mode:
plain: 'žádný'
simple_tls: 'simple_tls'
start_tls: 'start_tls'
plain_description: "Plain unencrypted connection, no TLS negotiation."
simple_tls_description: "Implicit TLS encryption, but no certificate validation. Use with caution and implicit trust of the LDAP connection."
start_tls_description: "Explicit TLS encryption with full validation. Use for LDAP over TLS/SSL."
simple_tls: 'jednoduché_tls'
start_tls: 'začátek_tls'
plain_description: "Prosté nešifrované připojení, žádné jednání TLS."
simple_tls_description: "Implicitní TLS šifrování, ale bez ověření certifikátu. Používejte s opatrností a implicitní důvěru k LDAP připojení."
start_tls_description: "Explicitní TLS šifrování s úplnou validací. Použijte pro LDAP nad TLS/SSL."
section_more_info_link_html: >
This section concerns the connection security of this LDAP authentication source. For more information, visit <a href="%{link}">the Net::LDAP documentation</a>.
Tato sekce se týká zabezpečení připojení tohoto LDAP autentifikačního zdroje. Pro více informací navštivte <a href="%{link}">síť:LDAP dokumentaci</a>.
forums:
show:
no_results_title_text: There are currently no posts for the forum.
@ -89,7 +89,7 @@ cs:
name: 'Akce'
add: 'Přidat akci'
assigned_to:
executing_user_value: '(Assign to executing user)'
executing_user_value: '(Přiřaďte k vykonání uživatele)'
conditions: 'Podmínky'
plural: 'Vlastní akce'
new: 'Nová vlastní akce'
@ -100,11 +100,11 @@ cs:
description: 'Vlastní akce zjednodušuje každodenní práci tím, že kombinuje několik kroků do jednoho tlačítka.'
custom_fields:
text_add_new_custom_field: >
To add new custom fields to a project you first need to create them before you can add them to this project.
Chcete-li přidat nová vlastní pole do projektu, musíte je nejprve vytvořit, než je budete moci přidat do tohoto projektu.
is_enabled_globally: 'Globálně povoleno'
enabled_in_project: 'Povoleno v projektu'
contained_in_type: 'Contained in type'
confirm_destroy_option: "Deleting an option will delete all of its occurrences (e.g. in work packages). Are you sure you want to delete it?"
contained_in_type: 'Obsahuje typ'
confirm_destroy_option: "Smazáním možnosti smažete všechny výskyty (např. v pracovních balíčcích). Opravdu ji chcete odstranit?"
tab:
no_results_title_text: V současné době nejsou žádná vlastní pole.
no_results_content_text: Vytvořit nové vlastní pole
@ -1115,14 +1115,14 @@ cs:
community: "OpenProject komunita"
upsale:
become_hero: "Staň se hrdinou!"
title: "Upgrade to Enterprise Edition"
title: "Upgradovat na Enterprise Edition"
description: "What are the benefits?"
more_info: "Více informací"
additional_features: "Additional powerful premium features"
professional_support: "Professional support from the OpenProject experts"
you_contribute: "Developers need to pay their bills, too. With Enterprise Edition you substantially contribute to this Open-Source community effort."
links:
upgrade_enterprise_edition: "Upgrade to Enterprise Edition"
upgrade_enterprise_edition: "Upgradovat na Enterprise Edition"
postgres_migration: "Migrating your installation to PostgreSQL"
user_guides: "Uživatelské příručky"
faq: "FAQ - často kladené dotazy"

@ -445,8 +445,8 @@ fr:
confirmation: "ne correspond pas à %{attribute}."
could_not_be_copied: "n'a pas pu être copié (entièrement)."
does_not_exist: "n'existe pas."
error_unauthorized: "may not be accessed."
error_readonly: "is not writable."
error_unauthorized: "est interdit d'accès."
error_readonly: "est en lecture seule."
empty: "ne peut pas être vide."
even: "doit être pair."
exclusion: "est réservé."

@ -43,7 +43,7 @@ cs:
button_close: "Zavřít"
button_change_project: "Change project"
button_check_all: "Zaškrtnout vše"
button_configure-form: "Configure form"
button_configure-form: "Nastavení formuláře"
button_confirm: "Potvrdit"
button_continue: "Pokračovat"
button_copy: "Kopírovat"
@ -54,13 +54,13 @@ cs:
button_duplicate: "Kopie"
button_edit: "Upravit"
button_filter: "Filtr"
button_advanced_filter: "Advanced filter"
button_advanced_filter: "Pokročilý filtr"
button_list_view: "Zobrazit jako seznam"
button_show_view: "Celá obrazovka"
button_log_time: "Čas protokolu"
button_more: "Více"
button_open_details: "Open details view"
button_close_details: "Close details view"
button_open_details: "Otevřít zobrazení podrobností"
button_close_details: "Zavřít zobrazení podrobností"
button_open_fullscreen: "Otevřít celoobrazovkový pohled"
button_show_cards: "Show card view"
button_show_list: "Show list view"
@ -106,16 +106,16 @@ cs:
description_selected_columns: "Vybrané sloupce"
description_subwork_package: "Potomek pracovního balíčku #%{id}"
editor:
preview: 'Toggle preview mode'
source_code: 'Toggle Markdown source mode'
error_saving_failed: 'Saving the document failed with the following error: %{error}'
preview: 'Přepnout režim náhledu'
source_code: 'Přepnout zdrojový mód Markdown'
error_saving_failed: 'Uložení dokumentu se nezdařilo s následující chybou: %{error}'
error_initialization_failed: 'Nepodařilo se inicializovat CKEditor!'
mode:
manual: 'Přepněte do Markdown zdroje'
wysiwyg: 'Switch to WYSIWYG editor'
wysiwyg: 'Přepnout na WYSIWYG Editor'
macro:
child_pages:
button: 'Links to child pages'
button: 'Odkazy na podřízené stránky'
include_parent: 'Include parent'
text: '[Placeholder] Links to child pages of'
page: 'Stránka wiki'
@ -419,7 +419,7 @@ cs:
reset_title: "Reset form configuration"
confirm_reset: >
Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields.
upgrade_to_ee: "Upgrade to Enterprise Edition"
upgrade_to_ee: "Upgradovat na Enterprise Edition"
upgrade_to_ee_text: "Wow! If you need this feature you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise Edition client?"
more_information: "Více informací"
nevermind: "Nevermind"

@ -2576,7 +2576,7 @@ tr:
redirect_uri_html: >
İzin verilen URL'ler yetkili kullanıcılar adresine yönlendirilebilir. Her satıra bir giriş. <br/> Bir masaüstü uygulamasına kaydoluyorsanız, aşağıdaki URL'yi kullanın.
confidential: "Uygulamanın, müşteri sırrının gizli tutulabileceği yerlerde kullanılıp kullanılmayacağını kontrol edin. Yerel mobil uygulamalar ve Tek Sayfa Uygulamaları gizli değildir."
scopes: "Check the scopes you want the application to grant access to. If no scope is checked, api_v3 is assumed."
scopes: "Uygulamanın erişmesine izin vermek istediğiniz kapsamları kontrol edin. Kapsam işaretli değilse, api_v3 olduğu kabul edilir."
client_credential_user_id: "İstemciler bu uygulamayı kullandıklarında kimliğe bürünmek için isteğe bağlı kullanıcı kimliği. Yalnızca genel erişime izin vermek için boş bırakın"
register_intro: "OpenProject için bir OAuth API istemci uygulaması geliştiriyorsanız, tüm kullanıcıların kullanması için bu formu kullanarak kayıt olabilirsiniz."
default_scopes: ""
@ -2591,8 +2591,8 @@ tr:
wants_to_access_html: >
Bu uygulama OpenProject hesabınıza erişim istiyor. <br/> <strong> Aşağıdaki izinleri istedi: </strong>
scopes:
api_v3: "Full API v3 access"
api_v3_text: "Application will receive full read & write access to the OpenProject API v3 to perform actions on your behalf."
api_v3: "Tam API v3 erişimi"
api_v3_text: "Uygulama, sizin adınıza işlem yapmak için OpenProject API v3'e tam okuma ve yazma erişimi alacaktır."
grants:
created_date: "Tarihinde onaylandı"
scopes: "İzinler"

@ -497,7 +497,7 @@ en:
could_not_be_copied: "could not be (fully) copied."
does_not_exist: "does not exist."
error_unauthorized: "may not be accessed."
error_readonly: "is not writable."
error_readonly: "was attempted to be written but is not writable."
empty: "can't be empty."
even: "must be even."
exclusion: "is reserved."
@ -2652,7 +2652,6 @@ en:
estimated_hours: "Estimated hours cannot be set on parent work packages."
invalid_user_assigned_to_work_package: "The chosen user is not allowed to be '%{property}' for this work package."
start_date: "Start date cannot be set on parent work packages."
writing_read_only_attributes: "You must not write a read-only attribute."
resources:
schema: 'Schema'

@ -0,0 +1,5 @@
class AddContentOrientationToCustomFields < ActiveRecord::Migration[6.0]
def change
add_column :custom_fields, :content_right_to_left, :boolean, default: false
end
end

@ -3,12 +3,15 @@ import {CardEventHandler} from "core-components/wp-card-view/event-handler/card-
import {WorkPackageCardViewComponent} from "core-components/wp-card-view/wp-card-view.component";
import {WorkPackageViewSelectionService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-selection.service";
import {WorkPackageViewFocusService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-focus.service";
import {WorkPackageCardViewService} from "core-components/wp-card-view/services/wp-card-view.service";
import {StateService} from "@uirouter/core";
import {DeviceService} from "core-app/modules/common/browser/device.service";
export class CardClickHandler implements CardEventHandler {
// Injections
public deviceService:DeviceService = this.injector.get(DeviceService);
public $state:StateService = this.injector.get(StateService);
public wpTableSelection:WorkPackageViewSelectionService = this.injector.get(WorkPackageViewSelectionService);
public wpTableFocus:WorkPackageViewFocusService = this.injector.get(WorkPackageViewFocusService);
public wpCardView:WorkPackageCardViewService = this.injector.get(WorkPackageCardViewService);
@ -67,7 +70,15 @@ export class CardClickHandler implements CardEventHandler {
// not matter what other card are (de-)selected below.
// Thus save that card for the details view button.
this.wpTableFocus.updateFocus(wpId);
// open work package on mobile after first click
if (this.deviceService.isMobile) {
this.$state.go(
'work-packages.show',
{workPackageId: wpId}
);
}
return false;
}
}

@ -33,6 +33,7 @@ import {CardViewHandlerRegistry} from "core-components/wp-card-view/event-handle
import {WorkPackageCardViewService} from "core-components/wp-card-view/services/wp-card-view.service";
import {WorkPackageCardDragAndDropService} from "core-components/wp-card-view/services/wp-card-drag-and-drop.service";
import {WorkPackageNotificationService} from "core-app/modules/work_packages/notifications/work-package-notification.service";
import {DeviceService} from "core-app/modules/common/browser/device.service";
export type CardViewOrientation = 'horizontal'|'vertical';
@ -100,7 +101,8 @@ export class WorkPackageCardViewComponent implements OnInit, AfterViewInit {
readonly wpTableSelection:WorkPackageViewSelectionService,
readonly wpViewOrder:WorkPackageViewOrderService,
readonly cardView:WorkPackageCardViewService,
readonly cardDragDrop:WorkPackageCardDragAndDropService) {
readonly cardDragDrop:WorkPackageCardDragAndDropService,
readonly deviceService:DeviceService) {
}
ngOnInit() {
@ -140,9 +142,12 @@ export class WorkPackageCardViewComponent implements OnInit, AfterViewInit {
}
ngAfterViewInit() {
// Register Drag & Drop
this.cardDragDrop.init(this);
this.cardDragDrop.registerDragAndDrop();
// Register Drag & Drop only on desktop
if (!this.deviceService.isMobile) {
this.cardDragDrop.registerDragAndDrop();
}
// Register event handlers for the cards
new CardViewHandlerRegistry(this.injector).attachTo(this);

@ -5,7 +5,7 @@
[ngClass]="cardHighlightingClass(workPackage)">
</div>
<div class="wp-card--inline-buttons">
<div class="wp-card--inline-buttons hidden-for-mobile">
<a class="wp-card--inline-cancel-button -no-decoration"
*ngIf="workPackage.isNew || showRemoveButton"
[ngClass]="{ '-show': workPackage.isNew }"

@ -85,3 +85,7 @@
flex-basis: 200px
object-fit: cover
@media only screen and (max-width: 679px)
.wp-card
max-width: none

@ -61,7 +61,7 @@ export class DisplayFieldRenderer<T extends HalResource = HalResource> {
}
const field = this.getField(resource, fieldSchema, schemaName, change);
field.render(span, this.getText(field, fieldSchema, placeholder));
field.render(span, this.getText(field, fieldSchema, placeholder), fieldSchema.options);
const title = field.title;
if (title) {

@ -28,6 +28,10 @@ export interface ICKEditorContext {
removePlugins?:string[];
// Set of enabled macro plugins or false to disable all
macros?:'none'|'wp'|'full'|boolean|string[];
// Additional options like the text orientation of the editors content
options?:{
rtl?:boolean;
};
// context link to append on preview requests
previewContext?:string;
}
@ -61,10 +65,15 @@ export class CKEditorSetupService {
const toolbarWrapper = wrapper.querySelector('.document-editor__toolbar') as HTMLElement;
const contentWrapper = wrapper.querySelector('.document-editor__editable') as HTMLElement;
var contentLanguage = context.options && context.options.rtl ? 'ar' : 'en';
return editor
.createCustomized(contentWrapper, {
openProject: this.createConfig(context),
initialData: initialData
initialData: initialData,
language: {
content: contentLanguage
}
})
.then((editor) => {
// Add decoupled toolbar

@ -118,7 +118,7 @@ export class DisplayField<T extends HalResource = HalResource> extends Field {
return this.valueString;
}
public render(element:HTMLElement, displayText:string):void {
public render(element:HTMLElement, displayText:string, options:any = {}):void {
element.textContent = displayText;
}

@ -35,9 +35,14 @@ export class FormattableDisplayField extends DisplayField {
private readonly appRef = this.$injector.get(ApplicationRef);
public render(element:HTMLElement, displayText:string):void {
public render(element:HTMLElement, displayText:string, options:any = {}):void {
let div = document.createElement('div');
div.classList.add('read-value--html', 'wiki', 'highlight', '-multiline');
if (options.rtl) {
div.classList.add('-rtl');
}
div.innerHTML = displayText;
element.innerHTML = '';

@ -116,7 +116,8 @@ export class FormattableEditFieldComponent extends EditFieldComponent implements
return {
resource: this.resource,
macros: 'none' as 'none',
previewContext: this.previewContext
previewContext: this.previewContext,
options: { rtl: this.schema.options && this.schema.options.rtl }
};
}

@ -42,7 +42,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
@ViewChild(NgSelectComponent, { static: true }) public ngSelectComponent:NgSelectComponent;
readonly I18n:I18nService = this.injector.get(I18nService);
public options:any[] = [];
public availableOptions:any[] = [];
public valueOptions:ValueOption[];
public text = {
requiredPlaceholder: this.I18n.t('js.placeholders.selection'),
@ -70,7 +70,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
untilComponentDestroyed(this),
)
.subscribe(() => {
this.requestFocus = this.options.length === 0;
this.requestFocus = this.availableOptions.length === 0;
// If we already have all values loaded, open now.
if (!this.requestFocus) {
@ -108,7 +108,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
public set selectedOption(val:ValueOption[]) {
this._selectedOption = val;
let mapper = (val:ValueOption) => {
let option = _.find(this.options, o => o.$href === val.$href) || this.nullOption;
let option = _.find(this.availableOptions, o => o.$href === val.$href) || this.nullOption;
// Special case 'null' value, which angular
// only understands in ng-options as an empty string.
@ -166,14 +166,14 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
});
}
this.options = availableValues || [];
this.valueOptions = this.options.map(el => {
this.availableOptions = availableValues || [];
this.valueOptions = this.availableOptions.map(el => {
return { name: el.name, $href: el.$href };
});
this._selectedOption = this.buildSelectedOption();
this.checkCurrentValueValidity();
if (this.options.length > 0 && this.requestFocus) {
if (this.availableOptions.length > 0 && this.requestFocus) {
this.openAutocompleteSelectField();
this.requestFocus = false;
}
@ -209,7 +209,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
// (If value AND)
// MultiSelect AND there is no value which href is not in the options hrefs
(!_.some(this.value, (value:HalResource) => {
return _.some(this.options, (option) => (option.$href === value.$href))
return _.some(this.availableOptions, (option) => (option.$href === value.$href))
}))
);
}

@ -48,7 +48,7 @@ export interface ValueOption {
export class SelectEditFieldComponent extends EditFieldComponent implements OnInit {
public selectAutocompleterRegister = this.injector.get(SelectAutocompleterRegisterService);
public options:any[];
public availableOptions:any[];
public valueOptions:ValueOption[];
public text:{ requiredPlaceholder:string, placeholder:string };
@ -104,7 +104,7 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
}
public set selectedOption(val:ValueOption) {
let option = _.find(this.options, o => o.$href === val.$href);
let option = _.find(this.availableOptions, o => o.$href === val.$href);
// Special case 'null' value, which angular
// only understands in ng-options as an empty string.
@ -116,9 +116,9 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
}
private setValues(availableValues:HalResource[]) {
this.options = this.halSorting.sort(availableValues);
this.availableOptions = this.halSorting.sort(availableValues);
this.addEmptyOption();
this.valueOptions = this.options.map(el => {
this.valueOptions = this.availableOptions.map(el => {
return {name: el.name, $href: el.$href};
});
}
@ -138,13 +138,13 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
}
private addValue(val:HalResource) {
this.options.push(val);
this.availableOptions.push(val);
this.valueOptions.push({name: val.name, $href: val.$href});
}
public get currentValueInvalid():boolean {
return !!(
(this.value && !_.some(this.options, (option:HalResource) => (option.$href === this.value.$href)))
(this.value && !_.some(this.availableOptions, (option:HalResource) => (option.$href === this.value.$href)))
||
(!this.value && this.schema.required)
);
@ -191,7 +191,7 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
// the option if one is returned / exists already.
const emptyOption = this.getEmptyOption();
if (emptyOption === undefined) {
this.options.unshift({
this.availableOptions.unshift({
name: this.text.placeholder,
$href: ''
});
@ -199,6 +199,6 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
}
private getEmptyOption():ValueOption|undefined {
return _.find(this.options, el => el.name === this.text.placeholder);
return _.find(this.availableOptions, el => el.name === this.text.placeholder);
}
}

@ -36,6 +36,7 @@ export interface IFieldSchema {
required?:boolean;
hasDefault:boolean;
name?:string;
options?:any;
}
export class Field {
@ -69,6 +70,10 @@ export class Field {
return this.schema.hasDefault;
}
public get options():boolean {
return this.schema.options;
}
public isEmpty():boolean {
return !this.value;
}

@ -123,7 +123,7 @@ export class WorkPackagesListComponent extends WorkPackagesViewBase implements O
this.currentQuery = query;
// Update the visible representation
if (this.wpDisplayRepresentation.valueFromQuery(query) === wpDisplayCardRepresentation) {
if (this.deviceService.isMobile || this.wpDisplayRepresentation.valueFromQuery(query) === wpDisplayCardRepresentation) {
this.showListView = false;
} else {
this.showListView = true;

@ -55,6 +55,7 @@ import {WorkPackageViewDisplayRepresentationService} from "core-app/modules/work
import {HalEvent, HalEventsService} from "core-app/modules/hal/services/hal-events.service";
import {HalResourceNotificationService} from "core-app/modules/hal/services/hal-resource-notification.service";
import {WorkPackageNotificationService} from "core-app/modules/work_packages/notifications/work-package-notification.service";
import {DeviceService} from "core-app/modules/common/browser/device.service";
export abstract class WorkPackagesViewBase implements OnInit, OnDestroy {
@ -83,6 +84,7 @@ export abstract class WorkPackagesViewBase implements OnInit, OnDestroy {
readonly cdRef:ChangeDetectorRef = this.injector.get(ChangeDetectorRef);
readonly wpDisplayRepresentation:WorkPackageViewDisplayRepresentationService = this.injector.get(WorkPackageViewDisplayRepresentationService);
readonly halEvents:HalEventsService = this.injector.get(HalEventsService);
readonly deviceService:DeviceService = this.injector.get(DeviceService);
constructor(protected injector:Injector) {

@ -46,7 +46,6 @@ module API
required: true,
has_default: false,
writable: true,
visibility: nil,
attribute_group: nil,
current_user: nil,
allowed_values_getter: nil)
@ -59,7 +58,6 @@ module API
required: required,
has_default: has_default,
writable: writable,
visibility: visibility,
attribute_group: attribute_group,
current_user: current_user)
end

@ -35,18 +35,13 @@ module API
class PropertySchemaRepresenter < ::API::Decorators::Single
def initialize(
type:, name:, required: true, has_default: false, writable: true,
visibility: nil, attribute_group: nil, current_user: nil
attribute_group: nil, current_user: nil
)
@type = type
@name = name
@required = required
@has_default = has_default
@writable = writable
@visibility = if visibility == false
nil
else
visibility || 'default'
end
@attribute_group = attribute_group
super(nil, current_user: current_user)
@ -57,22 +52,22 @@ module API
:required,
:has_default,
:writable,
:visibility,
:attribute_group,
:min_length,
:max_length,
:regular_expression
:regular_expression,
:options
property :type, exec_context: :decorator
property :name, exec_context: :decorator
property :required, exec_context: :decorator
property :has_default, exec_context: :decorator
property :writable, exec_context: :decorator
property :visibility, exec_context: :decorator
property :attribute_group, exec_context: :decorator
property :min_length, exec_context: :decorator
property :max_length, exec_context: :decorator
property :regular_expression, exec_context: :decorator
property :options, exec_context: :decorator
private

@ -58,11 +58,11 @@ module API
required: true,
has_default: false,
writable: default_writable_property(property),
visibility: nil,
attribute_group: nil,
min_length: nil,
max_length: nil,
regular_expression: nil,
options: {},
show_if: true)
getter = ->(*) do
schema_property_getter(type,
@ -70,11 +70,11 @@ module API
required,
has_default,
writable,
visibility,
attribute_group,
min_length,
max_length,
regular_expression)
regular_expression,
options)
end
schema_property(property,
@ -94,7 +94,6 @@ module API
required: true,
has_default: false,
writable: default_writable_property(property),
visibility: nil,
attribute_group: nil,
show_if: true)
getter = ->(*) do
@ -103,7 +102,6 @@ module API
required,
has_default,
writable,
visibility,
attribute_group,
href_callback)
end
@ -129,7 +127,6 @@ module API
required: true,
has_default: false,
writable: default_writable_property(property),
visibility: nil,
attribute_group: nil,
show_if: true)
@ -142,7 +139,6 @@ module API
required,
has_default,
writable,
visibility,
attribute_group,
values_callback,
nil)
@ -167,7 +163,6 @@ module API
required: true,
has_default: false,
writable: default_writable_property(property),
visibility: nil,
attribute_group: nil,
show_if: true)
getter = ->(*) do
@ -179,7 +174,6 @@ module API
required,
has_default,
writable,
visibility,
attribute_group,
values_callback,
->(*) {
@ -282,11 +276,11 @@ module API
required,
has_default,
writable,
visibility,
attribute_group,
min_length,
max_length,
regular_expression)
regular_expression,
options)
name = call_or_translate(name_source)
schema = ::API::Decorators::PropertySchemaRepresenter
.new(type: call_or_use(type),
@ -294,11 +288,11 @@ module API
required: call_or_use(required),
has_default: call_or_use(has_default),
writable: call_or_use(writable),
visibility: call_or_use(visibility),
attribute_group: call_or_use(attribute_group))
schema.min_length = min_length
schema.max_length = max_length
schema.regular_expression = regular_expression
schema.options = options
schema
end
@ -308,7 +302,6 @@ module API
required,
has_default,
writable,
visibility,
attribute_group,
href_callback)
representer = ::API::Decorators::AllowedValuesByLinkRepresenter
@ -317,7 +310,6 @@ module API
required: call_or_use(required),
has_default: call_or_use(has_default),
writable: call_or_use(writable),
visibility: call_or_use(visibility),
attribute_group: call_or_use(attribute_group))
if form_embedded
@ -335,7 +327,6 @@ module API
required,
has_default,
writable,
visibility,
attribute_group,
values_callback,
allowed_values_getter)
@ -354,7 +345,6 @@ module API
required: call_or_use(required),
has_default: call_or_use(has_default),
writable: call_or_use(writable),
visibility: call_or_use(visibility),
attribute_group: call_or_use(attribute_group) }
attributes[:allowed_values_getter] = allowed_values_getter if allowed_values_getter

@ -89,7 +89,7 @@ module API
api_attribute_name = ::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
errors.symbols_and_messages_for(attribute).each do |symbol, full_message, _|
api_errors << if symbol == :error_readonly
::API::Errors::UnwritableProperty.new(api_attribute_name)
::API::Errors::UnwritableProperty.new(api_attribute_name, full_message)
else
::API::Errors::Validation.new(api_attribute_name, full_message)
end

@ -33,8 +33,8 @@ module API
identifier 'urn:openproject-org:api:v3:errors:PropertyIsReadOnly'
code 422
def initialize(property)
super I18n.t('api_v3.errors.writing_read_only_attributes')
def initialize(property, message)
super message
@property = property
@details = { attribute: property }

@ -43,12 +43,16 @@ module API
end
def present_error(call)
errors = call.errors
errors = merge_dependent_errors call if errors.empty?
api_errors = [::API::Errors::ErrorBase.create_and_merge_errors(postprocess_errors(call))]
api_errors = [::API::Errors::ErrorBase.create_and_merge_errors(errors)]
fail(::API::Errors::MultipleErrors
.create_if_many(api_errors))
end
fail ::API::Errors::MultipleErrors.create_if_many(api_errors)
def postprocess_errors(call)
errors = call.errors
errors = merge_dependent_errors call if errors.empty?
errors
end
def merge_dependent_errors(call)

@ -39,17 +39,14 @@ module API
end
schema :id,
type: 'Integer',
visibility: false
type: 'Integer'
schema :created_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
schema_with_allowed_link :project,
has_default: false,
required: true,
visibility: false,
href_callback: ->(*) {
allowed_projects_href
}
@ -57,7 +54,6 @@ module API
schema_with_allowed_link :principal,
has_default: false,
required: true,
visibility: false,
href_callback: ->(*) {
allowed_principal_href
}
@ -67,7 +63,6 @@ module API
name_source: :role,
has_default: false,
required: true,
visibility: false,
href_callback: ->(*) {
api_v3_paths.path_for(:roles, filters: [{ unit: { operator: '=', values: ['project'] } }])
}

@ -37,45 +37,37 @@ module API
custom_field_injector type: :schema_representer
schema :id,
type: 'Integer',
visibility: false
type: 'Integer'
schema :name,
type: 'String',
visibility: false,
min_length: 1,
max_length: 255
schema :identifier,
type: 'String',
visibility: false,
min_length: 1,
max_length: 100
schema :description,
type: 'Formattable',
visibility: false,
required: false
schema :public,
type: 'Boolean',
visibility: false
type: 'Boolean'
schema :active,
type: 'Boolean',
visibility: false
type: 'Boolean'
schema :status,
type: 'ProjectStatus',
name_source: ->(*) { I18n.t('activerecord.attributes.project/status.code') },
visibility: false,
required: false,
writable: ->(*) { represented.writable?(:status) }
schema :status_explanation,
type: 'Formattable',
name_source: ->(*) { I18n.t('activerecord.attributes.project/status.explanation') },
visibility: false,
required: false,
writable: ->(*) { represented.writable?(:status) }
@ -93,12 +85,10 @@ module API
}
schema :created_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
schema :updated_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
def self.represented_class
::Project

@ -40,7 +40,6 @@ module API
writable: true,
has_default: false,
required: true,
visibility: false,
values_callback: ->(*) {
represented.custom_field.custom_options
},

@ -46,7 +46,6 @@ module API
writable: true,
has_default: false,
required: true,
visibility: false,
href_callback: ->(*) {
href_callback
},

@ -41,8 +41,7 @@ module API
type: 'String',
writable: false,
has_default: true,
required: true,
visibility: false
required: true
def self.filter_representer
::API::V3::Queries::Filters::QueryFilterRepresenter
@ -61,7 +60,6 @@ module API
type: 'QueryFilter',
required: true,
writable: true,
visibility: false,
values_callback: -> {
[filter]
},
@ -86,7 +84,6 @@ module API
writable: true,
has_default: false,
required: true,
visibility: false,
values_callback: -> {
filter.available_operators
},

@ -61,34 +61,28 @@ module API
end
schema :id,
type: 'Integer',
visibility: false
type: 'Integer'
schema :name,
type: 'String',
writable: true,
min_length: 1,
max_length: 255,
visibility: false
max_length: 255
schema :created_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
schema :updated_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
schema :user,
type: 'User',
has_default: true,
visibility: false
has_default: true
schema_with_allowed_link :project,
type: 'Project',
required: false,
writable: true,
visibility: false,
href_callback: ->(*) {
api_v3_paths.query_available_projects
}
@ -100,85 +94,73 @@ module API
represented.project,
global: represented.project.nil?)
end,
has_default: true,
visibility: false
has_default: true
schema :sums,
type: 'Boolean',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :timeline_visible,
type: 'Boolean',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :timeline_zoom_level,
type: 'String',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :timeline_labels,
type: 'QueryTimelineLabels',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :highlighting_mode,
type: 'String',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :display_representation,
type: 'String',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :show_hierarchies,
type: 'Boolean',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :starred,
type: 'Boolean',
required: false,
writable: false,
has_default: true,
visibility: false
has_default: true
schema :hidden,
type: 'Boolean',
required: true,
writable: true,
has_default: true,
visibility: false
has_default: true
schema :ordered_work_packages,
type: 'QueryOrder',
required: false,
writable: true,
has_default: true,
visibility: false
has_default: true
schema_with_allowed_collection :columns,
type: '[]QueryColumn',
required: false,
writable: true,
has_default: true,
visibility: false,
values_callback: -> { represented.available_columns },
value_representer: ->(column) {
Columns::QueryColumnsFactory.representer(column)
@ -204,7 +186,6 @@ module API
type: '[]QueryGroupBy',
required: false,
writable: true,
visibility: false,
values_callback: -> { represented.groupable_columns },
value_representer: GroupBys::QueryGroupByRepresenter,
link_factory: ->(column) {
@ -221,7 +202,6 @@ module API
required: false,
writable: true,
has_default: true,
visibility: false,
values_callback: -> { represented.available_highlighting_columns },
value_representer: ->(column) {
Columns::QueryColumnsFactory.representer(column)
@ -241,7 +221,6 @@ module API
required: false,
writable: true,
has_default: true,
visibility: false,
values_callback: -> do
values = represented.sortable_columns.map do |column|
[SortBys::SortByDecorator.new(column, 'asc'),
@ -263,8 +242,7 @@ module API
schema :results,
type: 'WorkPackageCollection',
required: false,
writable: false,
visibility: false
writable: false
property :filters_schemas,
embedded: true,

@ -188,7 +188,8 @@ module API
writable: writable,
min_length: cf_min_length(custom_field),
max_length: cf_max_length(custom_field),
regular_expression: cf_regexp(custom_field)
regular_expression: cf_regexp(custom_field),
options: cf_options(custom_field)
end
def path_method_for(custom_field)
@ -321,6 +322,12 @@ module API
custom_field.regexp unless custom_field.regexp.blank?
end
def cf_options(custom_field)
{
rtl: ("true" if custom_field.content_right_to_left)
}
end
def list_schemas_values_callback(custom_field)
->(*) { represented.assignable_custom_field_values(custom_field) }
end

@ -45,30 +45,25 @@ module API
end
schema :id,
type: 'Integer',
visibility: false
type: 'Integer'
schema :name,
type: 'String',
min_length: 1,
max_length: 60,
visibility: false
max_length: 60
schema :description,
type: 'Formattable',
required: false,
visibility: false
required: false
schema :start_date,
type: 'Date',
required: false,
visibility: false
required: false
schema :due_date,
as: 'endDate',
type: 'Date',
required: false,
visibility: false
required: false
schema_with_allowed_string_collection :status,
type: 'String'
@ -80,7 +75,6 @@ module API
as: :definingProject,
has_default: false,
required: true,
visibility: false,
href_callback: ->(*) {
next unless represented.new_record?
@ -88,12 +82,10 @@ module API
}
schema :created_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
schema :updated_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
def self.represented_class
Version

@ -45,7 +45,6 @@ module API
writable: true,
has_default: false,
required: true,
visibility: false,
values_callback: ->(*) {
represented.allowed_values
},

@ -0,0 +1,43 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module Bcf::Issues
class BaseContract < ::ModelContract
attribute :uuid
attribute :work_package
attribute :index
def validate
validate_user_allowed_to_manage
super
end
end
end

@ -0,0 +1,39 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module Bcf::Issues
class CreateContract < BaseContract
private
def validate_user_allowed_to_manage
unless model.project && user.allowed_to?(:manage_bcf, model.project)
errors.add :base, :error_unauthorized
end
end
end
end

@ -0,0 +1,50 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Bcf::API::V2_1::Endpoints
class Create < API::Utilities::Endpoints::Create
include ModifyMixin
def present_success(_current_user, call)
render_representer
.new(call.result)
end
def postprocess_errors(call)
Bcf::API::V2_1::Errors::ErrorMapper.map(super)
end
private
def deduce_process_service
"::Bcf::#{deduce_backend_namespace}::#{update_or_create}Service".constantize
end
end
end

@ -0,0 +1,46 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Bcf::API::V2_1::Endpoints
module ModifyMixin
private
def deduce_parse_service
Bcf::API::V2_1::ParseResourceParamsService
end
def deduce_in_and_out_representer
"::Bcf::API::V2_1::#{deduce_api_namespace}::SingleRepresenter".constantize
end
alias_method :deduce_parse_representer, :deduce_in_and_out_representer
alias_method :deduce_render_representer, :deduce_in_and_out_representer
end
end

@ -30,22 +30,11 @@
module Bcf::API::V2_1::Endpoints
class Update < API::Utilities::Endpoints::Update
include ModifyMixin
def present_success(_current_user, call)
render_representer
.new(call.result)
end
private
def deduce_parse_service
Bcf::API::V2_1::ParseResourceParamsService
end
def deduce_in_and_out_representer
"::Bcf::API::V2_1::#{deduce_api_namespace}::SingleRepresenter".constantize
end
alias_method :deduce_parse_representer, :deduce_in_and_out_representer
alias_method :deduce_render_representer, :deduce_in_and_out_representer
end
end

@ -0,0 +1,41 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Bcf::API::V2_1
module ProjectExtensions
class API < ::API::OpenProjectAPI
get :extensions do
mapper = Definitions.new(project: @project, user: current_user)
Representer.new(mapper)
end
end
end
end

@ -53,6 +53,7 @@ module Bcf::API::V2_1
put &::Bcf::API::V2_1::Endpoints::Update.new(model: Project).mount
mount Bcf::API::V2_1::TopicsAPI
mount Bcf::API::V2_1::ProjectExtensions::API
end
end
end

@ -47,6 +47,25 @@ module Bcf::API::V2_1
scope: -> { topics })
.mount
post &::Bcf::API::V2_1::Endpoints::Create
.new(model: Bcf::Issue,
api_name: 'Topics',
params_modifier: ->(attributes) {
attributes[:project_id] = @project.id
wp_attributes = Bcf::Issues::TransformAttributesService
.new
.call(attributes)
.result
attributes
.slice(:stage,
:index,
:labels)
.merge(wp_attributes)
})
.mount
route_param :topic_uuid, regexp: /\A[a-f0-9\-]+\z/ do
after_validation do
@issue = topics.find_by_uuid!(params[:topic_uuid])

@ -141,9 +141,9 @@ module ::Bcf
raise(StandardError.new(I18n.t('bcf.exceptions.file_invalid')))
end
@issues = ::Bcf::Issue.with_markup
.includes(work_package: %i[status priority assigned_to])
.where(uuid: @listing.map { |e| e[:uuid] }, project: @project)
@issues = ::Bcf::Issue
.includes(work_package: %i[status priority assigned_to])
.where(uuid: @listing.map { |e| e[:uuid] }, project: @project)
render 'bcf/issues/diff_on_work_packages'
end

@ -9,120 +9,14 @@ module Bcf
after_update :invalidate_markup_cache
class << self
def with_markup
select '*',
extract_first_node(title_path, 'title'),
extract_first_node(description_path, 'description'),
extract_first_node(priority_text_path, 'priority_text'),
extract_first_node(status_text_path, 'status_text'),
extract_first_node(type_text_path, 'type_text'),
extract_first_node(assignee_text_path, 'assignee_text'),
extract_first_node(due_date_text_path, 'due_date_text'),
extract_first_node(creation_date_text_path, 'creation_date_text'),
extract_first_node(creation_author_text_path, 'creation_author_text'),
extract_first_node(modified_date_text_path, 'modified_date_text'),
extract_first_node(modified_author_text_path, 'modified_author_text'),
extract_first_node(index_text_path, 'index_text'),
extract_first_node(stage_text_path, 'stage_text'),
extract_nodes(labels_path, 'labels')
end
validates :work_package, presence: true
class << self
def of_project(project)
includes(:work_package)
.references(:work_packages)
.merge(WorkPackage.for_projects(project))
end
protected
def title_path
'/Markup/Topic/Title/text()'
end
def description_path
'/Markup/Topic/Description/text()'
end
def priority_text_path
'/Markup/Topic/Priority/text()'
end
def status_text_path
'/Markup/Topic/@TopicStatus'
end
def type_text_path
'/Markup/Topic/@TopicType'
end
def assignee_text_path
'/Markup/Topic/AssignedTo/text()'
end
def due_date_text_path
'/Markup/Topic/DueDate/text()'
end
def stage_text_path
'/Markup/Topic/Stage/text()'
end
def creation_date_text_path
'/Markup/Topic/CreationDate/text()'
end
def creation_author_text_path
'/Markup/Topic/CreationAuthor/text()'
end
def modified_date_text_path
'/Markup/Topic/ModifiedDate/text()'
end
def modified_author_text_path
'/Markup/Topic/ModifiedAuthor/text()'
end
def index_text_path
'/Markup/Topic/Index/text()'
end
def labels_path
'/Markup/Topic/Labels/text()'
end
private
def extract_first_node(path, as)
"(xpath('#{path}', markup))[1] AS #{as}"
end
def extract_nodes(path, as)
"(xpath('#{path}', markup)) AS #{as}"
end
end
%i[title
description
priority_text
status_text
type_text
assignee_text
due_date_text
creation_date_text
creation_author_text
modified_date_text
modified_author_text
stage_text
index_text].each do |name|
define_method name do
from_attributes_or_doc name
end
end
def labels
from_attributes_or_doc :labels, multiple: true
end
def markup_doc
@ -132,21 +26,5 @@ module Bcf
def invalidate_markup_cache
@markup_doc = nil
end
private
def from_attributes_or_doc(key, multiple: false)
if attributes.keys.include? key.to_s
self[key]
else
path = markup_doc.xpath(self.class.send("#{key}_path"))
if multiple
path.map(&:to_s)
else
path.first.to_s.presence
end
end
end
end
end

@ -0,0 +1,64 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Bcf::API::V2_1::Errors
class ErrorMapper
extend ActiveModel::Naming
extend ActiveModel::Translation
def read_attribute_for_validation(_attr)
nil
end
def self.lookup_ancestors
[::Bcf::Issue]
end
def self.map(original_errors)
mapped_errors = ActiveModel::Errors.new(new)
original_errors.send(:error_symbols).each do |key, errors|
errors.map(&:first).each do |error|
mapped_errors.add(error_key_mapper(key), error)
end
end
mapped_errors
end
def self.i18n_scope
:activerecord
end
def self.error_key_mapper(key)
{ subject: :title }[key] || key
end
end
end

@ -31,7 +31,9 @@
module Bcf::API::V2_1::Errors
class ErrorRepresenter < BaseRepresenter
property :message,
getter: ->(*) { message },
getter: ->(*) {
[message].concat(Array(errors).map(&:message)).compact.join(' ')
},
render_nil: true
end
end

@ -0,0 +1,110 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Bcf::API::V2_1
module ProjectExtensions
class Definitions
def initialize(project:, user:)
@project = project
@user = user
end
def topic_type
project.types.pluck(:name)
end
##
# We only return the default status for now
# since that can always be set to a new issue
def topic_status
Status
.where_default
.pluck(:name)
end
def priority
OpenProject::Cache.fetch(IssuePriority.all.cache_key, 'names') do
IssuePriority.all.pluck(:name)
end
end
def user_id_type
if allowed?(:view_members)
project.possible_assignees.pluck(:mail)
else
[]
end
end
# TODO: Labels do not yet exist
def topic_label
[]
end
# TODO: Stage do not yet exist
def stage
[]
end
# TODO: Snippet types do not exist
def snippet_type
[]
end
def project_actions
[].tap do |actions|
actions << 'update' if allowed?(:edit_project)
actions << 'createTopic' if allowed?(:manage_bcf)
end
end
def topic_actions
if allowed?(:manage_bcf)
%w[update updateRelatedTopics updateFiles createViewpoint]
else
[]
end
end
def comment_actions
[]
end
private
attr_reader :project, :user
def allowed?(permission)
user.allowed_to?(permission, project)
end
end
end
end

@ -0,0 +1,44 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Bcf::API::V2_1
class ProjectExtensions::Representer < BaseRepresenter
property :topic_type
property :topic_status
property :topic_label
property :snippet_type
property :priority
property :user_id_type
property :stage
property :project_actions
property :topic_actions
property :comment_actions
end
end

@ -35,45 +35,114 @@ module Bcf::API::V2_1
property :uuid,
as: :guid
property :type_text,
as: :topic_type
property :type,
as: :topic_type,
getter: ->(*) {
work_package
.type
.name
}
property :status_text,
as: :topic_status
property :status,
as: :topic_status,
getter: ->(*) {
work_package
.status
.name
}
property :priority,
as: :priority,
getter: ->(*) {
work_package
.priority
.name
}
property :reference_links,
getter: ->(decorator:, **) {
[decorator.api_v3_paths.work_package(work_package.id)]
}
property :title
property :title,
getter: ->(*) {
work_package.subject
}
property :index_text,
as: :index
property :index
property :labels
property :creation_date_text,
as: :creation_date
property :creation_date,
getter: ->(decorator:, **) {
decorator
.formatted_date_time(:created_at)
}
property :creation_author_text,
as: :creation_author
property :creation_author,
getter: ->(*) {
work_package
.author
.mail
}
property :modified_date_text,
as: :modified_date
property :modified_date,
getter: ->(decorator:, **) {
decorator
.formatted_date_time(:updated_at)
}
property :modified_author_text,
as: :modified_author
property :modified_author,
getter: ->(*) {
work_package
.journals
.max_by(&:version)
.user
.mail
}
property :assignee,
as: :assigned_to,
getter: ->(decorator:, **) {
decorator
.assigned_to
&.mail
}
property :stage
property :assignee_text,
as: :assigned_to
property :description,
getter: ->(*) {
work_package.description
}
property :due_date,
getter: ->(decorator:, **) {
decorator.datetime_formatter.format_date(work_package.due_date, allow_nil: true)
},
setter: ->(fragment:, decorator:, **) {
date = decorator
.datetime_formatter
.parse_date(fragment,
due_date,
allow_nil: true)
self.due_date = date
}
property :stage_text,
as: :stage
def datetime_formatter
::API::V3::Utilities::DateTimeFormatter
end
property :description
def formatted_date_time(method)
datetime_formatter
.format_datetime(represented.work_package.send(method), allow_nil: true)
end
property :due_date_text,
as: :due_date
def assigned_to
represented
.work_package
.assigned_to
end
end
end

@ -0,0 +1,51 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2019 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module Bcf::Issues
class CreateService < ::BaseServices::Create
private
def before_perform(params)
wp_call = WorkPackages::CreateService
.new(user: user)
.call(params)
if wp_call.success?
issue_params = {
work_package: wp_call.result
}.merge(params.slice(:stage, :labels, :index))
super(issue_params)
else
wp_call
end
end
end
end

@ -0,0 +1,34 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2019 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module Bcf::Issues
class SetAttributesService < ::BaseServices::SetAttributes
end
end

@ -0,0 +1,162 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2019 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module Bcf::Issues
class TransformAttributesService
def call(attributes)
ServiceResult.new success: true,
result: work_package_attributes(attributes)
end
private
##
# BCF issues might have empty titles. OP needs one.
def title(attributes)
if attributes[:title]
attributes[:title]
elsif attributes[:import_options]
'(Imported BCF issue contained no title)'
end
end
def author(project, attributes)
find_user_in_project(project, attributes[:author]) || User.system
end
def assignee(project, attributes)
assignee = find_user_in_project(project, attributes[:assignee])
return assignee if assignee.present?
missing_assignee(attributes[:assignee], attributes[:import_options] || {})
end
##
# Try to find the given user by mail in the project
def find_user_in_project(project, mail)
project.users.find_by(mail: mail)
end
def type(project, attributes)
type_name = attributes[:type]
type = project.types.find_by(name: type_name)
return type if type.present?
missing_type(project, type_name, attributes[:import_options] || {})
end
##
# Handle unknown statuses during import
def status(attributes)
status_name = attributes[:status]
status = ::Status.find_by(name: status_name)
return status if status.present?
missing_status(status_name, attributes[:import_options] || {})
end
##
# Handle unknown priorities during import
def priority(attributes)
priority_name = attributes[:priority]
priority = ::IssuePriority.find_by(name: priority_name)
return priority if priority.present?
missing_priority(priority_name, attributes[:import_options] || {})
end
##
# Get mapped and raw attributes from MarkupExtractor
# and return all values that are non-nil
def work_package_attributes(attributes)
project = Project.find(attributes[:project_id])
{
# Fixed attributes we know
project: project,
type: type(project, attributes),
# Native attributes from the extractor
subject: title(attributes),
description: attributes[:description],
due_date: attributes[:due_date],
start_date: attributes[:start_date],
# Mapped attributes
assigned_to: assignee(project, attributes),
status: status(attributes),
priority: priority(attributes)
}.compact
end
def missing_status(status_name, import_options)
if import_options[:unknown_statuses_action] == 'use_default'
::Status.default
elsif import_options[:unknown_statuses_action] == 'chose' &&
import_options[:unknown_statuses_chose_ids].any?
::Status.find_by(id: import_options[:unknown_statuses_chose_ids].first)
elsif status_name
Status::InexistentStatus.new
end
end
def missing_priority(priority_name, import_options)
if import_options[:unknown_priorities_action] == 'use_default'
# NOP The 'use_default' case gets already covered by OP.
elsif import_options[:unknown_priorities_action] == 'chose' &&
import_options[:unknown_priorities_chose_ids].any?
::IssuePriority.find_by(id: import_options[:unknown_priorities_chose_ids].first)
elsif priority_name
Priority::InexistentPriority.new
end
end
def missing_type(project, type_name, import_options)
if import_options[:unknown_types_action] == 'use_default'
project.types.default&.first
elsif import_options[:unknown_types_action] == 'chose' &&
import_options[:unknown_types_chose_ids].any?
project.types.find_by(id: import_options[:unknown_types_chose_ids].first)
elsif type_name
Type::InexistentType.new
end
end
def missing_assignee(assignee_name, import_options)
if import_options[:invalid_people_action] != 'anonymize' && assignee_name
User::InexistentUser.new
end
end
end
end

@ -63,5 +63,5 @@ tr:
permission_manage_bcf: "BCF sorunlarını içe aktarın ve yönetin"
oauth:
scopes:
bcf_v2_1: "Full access to the BCF v2.1 API"
bcf_v2_1_text: "Application will receive full read & write access to the OpenProject BCF API v2.1 to perform actions on your behalf."
bcf_v2_1: "BCF v2.1 API'sine tam erişim"
bcf_v2_1_text: "Uygulama, sizin adınıza işlem yapmak için OpenProject BCF API v2.1'e tam okuma ve yazma erişimi alacaktır."

@ -0,0 +1,9 @@
class AddIssueColumns < ActiveRecord::Migration[6.0]
def change
change_table :bcf_issues do |i|
i.string :stage
i.integer :index
i.text :labels, array: true, default: []
end
end
end

@ -155,8 +155,7 @@ module OpenProject::Bcf::BcfXml
zip,
entry,
current_user: current_user,
import_options: import_options,
aggregations: aggregations).extract!
import_options: import_options).extract!
if issue.errors.blank?
issue.save

@ -6,10 +6,10 @@ require_relative 'file_entry'
module OpenProject::Bcf::BcfXml
class IssueReader
attr_reader :zip, :entry, :issue, :extractor, :project, :user, :import_options, :aggregations
attr_reader :zip, :entry, :issue, :extractor, :project, :user, :import_options
attr_accessor :wp_last_updated_at, :is_update
def initialize(project, zip, entry, current_user:, import_options:, aggregations:)
def initialize(project, zip, entry, current_user:, import_options:)
@zip = zip
@entry = entry
@project = project
@ -17,23 +17,13 @@ module OpenProject::Bcf::BcfXml
@issue = find_or_initialize_issue
@extractor = MarkupExtractor.new(entry)
@import_options = import_options
@aggregations = aggregations
@doc = nil
@wp_last_updated_at = nil
@is_update = false
end
def extract!
@doc = extractor.doc
markup = extractor.doc.to_xml(indent: 2)
treat_empty_titles
treat_unknown_types
treat_unknown_statuses
treat_unknown_priorities
extractor.doc = @doc
markup = @doc.to_xml(indent: 2)
issue.markup = markup
extractor.markup = markup
@ -51,82 +41,6 @@ module OpenProject::Bcf::BcfXml
private
##
# BCF issues might have empty titles. OP needs one.
def treat_empty_titles
title_node = @doc.xpath('/Markup/Topic/Title').first
return if title_node&.content&.present?
title_node.content = "(Imported BCF issue contained no title)"
end
##
# Handle unknown types during import
def treat_unknown_types
if aggregations.unknown_types.present?
if import_options[:unknown_types_action] == 'use_default'
replace_type_with(::Type.default.first&.name)
elsif import_options[:unknown_types_action] == 'chose' && import_options[:unknown_types_chose_ids].any?
replace_type_with(::Type.find_by(id: import_options[:unknown_types_chose_ids].first)&.name)
else
raise StandardError.new 'Unknown topic type found in import. Use an existing type name.'
end
end
end
def replace_type_with(new_type_name)
raise StandardError.new "New type name can't be blank." unless new_type_name.present?
@doc.xpath('/Markup/Topic').first.set_attribute('TopicType', new_type_name)
end
##
# Handle unknown statuses during import
def treat_unknown_statuses
if aggregations.unknown_statuses.present?
if import_options[:unknown_statuses_action] == 'use_default'
replace_status_with(::Status.default&.name)
elsif import_options[:unknown_statuses_action] == 'chose' && import_options[:unknown_statuses_chose_ids].any?
replace_status_with(::Status.find_by(id: import_options[:unknown_statuses_chose_ids].first)&.name)
else
raise StandardError.new 'Unknown topic status found in import. Use an existing status name.'
end
end
end
def replace_status_with(new_status_name)
raise StandardError.new "New status name can't be blank." unless new_status_name.present?
@doc.xpath('/Markup/Topic').first.set_attribute('TopicStatus', new_status_name)
end
##
# Handle unknown priorities during import
def treat_unknown_priorities
if aggregations.unknown_priorities.present?
if import_options[:unknown_priorities_action] == 'use_default'
# NOP The 'use_default' case gets already covered by OP.
elsif import_options[:unknown_priorities_action] == 'chose' && import_options[:unknown_priorities_chose_ids].any?
replace_priorities_with(::IssuePriority.find_by(id: import_options[:unknown_priorities_chose_ids].first)&.name)
else
raise StandardError.new 'Unknown topic priority found in import. Use an existing priority name.'
end
end
end
def replace_priorities_with(new_priority_name)
raise StandardError.new "New priority name can't be blank." unless new_priority_name.present?
priority_node = @doc.xpath('/Markup/Topic/Priority').first
if priority_node
priority_node.content = new_priority_name
else
# Valid BCF XML Topics must have a Title node. So we can add the Priority node just behind it and thus,
# maintain the schema's sequence compliance.
@doc.at('/Markup/Topic/Title').after("<Priority>#{new_priority_name}</Priority>")
end
end
def synchronize_with_work_package
self.is_update = issue.work_package.present?
self.wp_last_updated_at = issue.work_package&.updated_at
@ -152,13 +66,9 @@ module OpenProject::Bcf::BcfXml
end
def create_work_package
call = WorkPackages::CreateService.new(user: user).call(work_package_attributes
.merge(send_notifications: false)
.symbolize_keys)
call = WorkPackages::CreateService.new(user: user).call(work_package_attributes)
if call.success?
force_overwrite(call.result)
end
force_overwrite(call.result) if call.success?
call
end
@ -167,62 +77,42 @@ module OpenProject::Bcf::BcfXml
find_user_in_project(extractor.author) || User.system
end
def assignee
find_user_in_project(extractor.assignee)
end
def type
type_name = extractor.type
type = ::Type.find_by(name: type_name)
return type if type.present?
return ::Type.default&.first if import_options[:unknown_types_action] == 'default'
if import_options[:unknown_types_action] == 'chose' &&
import_options[:unknown_types_chose_ids].any?
return ::Type.find_by(id: import_options[:unknown_types_chose_ids].first)
else
ServiceResult.new success: false,
errors: issue.errors,
result: issue
end
end
def start_date
extractor.creation_date.to_date unless is_update
end
def update_work_package
if import_is_newer?
WorkPackages::UpdateService
.new(user: user, model: issue.work_package)
.call(work_package_attributes.merge(send_notifications: false).symbolize_keys)
.call(work_package_attributes)
else
import_is_outdated(issue)
end
end
##
# Get mapped and raw attributes from MarkupExtractor
# and return all values that are non-nil
###
## Get mapped and raw attributes from MarkupExtractor
## and return all values that are non-nil
def work_package_attributes
{
# Fixed attributes we know
project: project,
type: type,
# Native attributes from the extractor
subject: extractor.title,
description: extractor.description,
due_date: extractor.due_date,
start_date: start_date,
# Mapped attributes
assigned_to: assignee,
status_id: statuses.fetch(extractor.status, statuses[:default]),
priority_id: priorities.fetch(extractor.priority, priorities[:default])
}.compact
attributes = ::Bcf::Issues::TransformAttributesService
.new
.call(extractor_attributes.merge(import_options: import_options))
.result
.merge(send_notifications: false)
.symbolize_keys
attributes[:start_date] = extractor.creation_date.to_date unless is_update
attributes
end
def extractor_attributes
attributes = {
project_id: project.id
}
%i(type title description due_date assignee status priority).each do |key|
attributes[key] = extractor.send(key)
end
attributes
end
##
@ -400,22 +290,10 @@ module OpenProject::Bcf::BcfXml
def update_journal_attributes(bcf_comment, comment_data)
bcf_comment.journal.update(notes: comment_data[:comment],
created_at: comment_data[:modified_date])
created_at: comment_data[:modified_date])
bcf_comment.journal.save
end
##
# Keep a hash map of current status ids for faster lookup
def statuses
@statuses ||= Hash[Status.pluck(:name, :id)].merge(default: Status.default.id)
end
##
# Keep a hash map of current status ids for faster lookup
def priorities
@priorities ||= Hash[IssuePriority.pluck(:name, :id)].merge(default: IssuePriority.default.try(:id))
end
def import_is_outdated(issue)
issue.errors.add :base,
:conflict,

@ -250,7 +250,7 @@ module OpenProject::Bcf::BcfXml
def find_or_initialize_issue
::Bcf::Issue.find_or_initialize_by(work_package: work_package)
end
def to_bcf_datetime(date_time)
date_time.utc.iso8601
end

@ -31,91 +31,12 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
member_in_project: project,
member_through_role: role)
end
let(:markup) do
<<-MARKUP
<Markup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Header>
<File IfcProject="0M6o7Znnv7hxsbWgeu7oQq" IfcSpatialStructureElement="23B$bNeGHFQuMYJzvUX0FD" isExternal="false">
<Filename>IfcPile_01.ifc</Filename>
<Date>2014-10-27T16:27:27Z</Date>
<Reference>../IfcPile_01.ifc</Reference>
</File>
</Header>
<Topic Guid="63E78882-7C6A-4BF7-8982-FC478AFB9C97" TopicType="Structural" TopicStatus="Open">
<ReferenceLink>https://bim--it.net</ReferenceLink>
<Title>Maximum Content</Title>
<Priority>High</Priority>
<Index>0</Index>
<Labels>Structural</Labels>
<Labels>IT Development</Labels>
<CreationDate>2015-06-21T12:00:00Z</CreationDate>
<CreationAuthor>mike@example.com</CreationAuthor>
<ModifiedDate>2015-06-21T14:22:47Z</ModifiedDate>
<ModifiedAuthor>mike@example.com</ModifiedAuthor>
<AssignedTo>andy@example.com</AssignedTo>
<Description>This is a topic with all information present.</Description>
<BimSnippet SnippetType="JSON">
<Reference>JsonElement.json</Reference>
<ReferenceSchema>http://json-schema.org</ReferenceSchema>
</BimSnippet>
<DocumentReference isExternal="true">
<ReferencedDocument>https://github.com/BuildingSMART/BCF-XML</ReferencedDocument>
<Description>GitHub BCF Specification</Description>
</DocumentReference>
<DocumentReference>
<ReferencedDocument>../markup.xsd</ReferencedDocument>
<Description>Markup.xsd Schema</Description>
</DocumentReference>
<RelatedTopic Guid="5019D939-62A4-45D9-B205-FAB602C98FE8" />
</Topic>
<Comment Guid="780FAE52-C432-42BE-ADEA-FF3E7A8CD8E1">
<Date>2015-08-31T12:40:17Z</Date>
<Author>mike@example.com</Author>
<Comment>This is an unmodified topic at the uppermost hierarchical level.
All times in the XML are marked as UTC times.</Comment>
</Comment>
<Comment Guid="897E4909-BDF3-4CC7-A283-6506CAFF93DD">
<Date>2015-08-31T14:00:01Z</Date>
<Author>mike@example.com</Author>
<Comment>This comment was a reply to the first comment in BCF v2.0. This is a no longer supported functionality and therefore is to be treated as a regular comment in v2.1.</Comment>
</Comment>
<Comment Guid="39C4B780-1B48-44E5-9802-D359007AA44E">
<Date>2015-08-31T13:07:11Z</Date>
<Author>mike@example.com</Author>
<Comment>This comment again is in the highest hierarchy level.
It references a viewpoint.</Comment>
<Viewpoint Guid="8dc86298-9737-40b4-a448-98a9e953293a" />
</Comment>
<Comment Guid="BD17158C-4267-4433-98C1-904F9B41CA50">
<Date>2015-08-31T15:42:58Z</Date>
<Author>mike@example.com</Author>
<Comment>This comment contained some spllng errs.
Hopefully, the modifier did catch them all.</Comment>
<ModifiedDate>2015-08-31T16:07:11Z</ModifiedDate>
<ModifiedAuthor>mike@example.com</ModifiedAuthor>
</Comment>
<Viewpoints Guid="8dc86298-9737-40b4-a448-98a9e953293a">
<Viewpoint>Viewpoint_8dc86298-9737-40b4-a448-98a9e953293a.bcfv</Viewpoint>
<Snapshot>Snapshot_8dc86298-9737-40b4-a448-98a9e953293a.png</Snapshot>
</Viewpoints>
<Viewpoints Guid="21dd4807-e9af-439e-a980-04d913a6b1ce">
<Viewpoint>Viewpoint_21dd4807-e9af-439e-a980-04d913a6b1ce.bcfv</Viewpoint>
<Snapshot>Snapshot_21dd4807-e9af-439e-a980-04d913a6b1ce.png</Snapshot>
</Viewpoints>
<Viewpoints Guid="81daa431-bf01-4a49-80a2-1ab07c177717">
<Viewpoint>Viewpoint_81daa431-bf01-4a49-80a2-1ab07c177717.bcfv</Viewpoint>
<Snapshot>Snapshot_81daa431-bf01-4a49-80a2-1ab07c177717.png</Snapshot>
</Viewpoints>
</Markup>
MARKUP
end
let(:bcf_issue) do
FactoryBot.create(:bcf_issue_with_comment, markup: markup)
let!(:bcf_issue) do
FactoryBot.create(:bcf_issue_with_comment, work_package: work_package)
end
let(:work_package) do
FactoryBot.create(:work_package,
project_id: project.id,
bcf_issue: bcf_issue)
project_id: project.id)
end
let(:representer) do
described_class.new(work_package,

@ -63,7 +63,7 @@ describe ::OpenProject::Bcf::BcfXml::Importer do
workflow
priority
bcf_manager_member
allow(User).to receive(:current).and_return(bcf_manager)
login_as(bcf_manager)
end
describe '#to_listing' do

@ -88,15 +88,13 @@ describe ::OpenProject::Bcf::BcfXml::IssueReader do
end
let(:entry_stream) { StringIO.new(markup) }
let(:import_options) { OpenProject::Bcf::BcfXml::Importer::DEFAULT_IMPORT_OPTIONS }
let(:aggregations) { OpenProject::Bcf::BcfXml::Aggregations.new([], project) }
subject do
described_class.new(project,
nil,
entry,
current_user: bcf_manager,
import_options: import_options,
aggregations: aggregations)
import_options: import_options)
end
before do
@ -134,12 +132,6 @@ describe ::OpenProject::Bcf::BcfXml::IssueReader do
end
context 'with no import options provided' do
let(:aggregations) do
Struct
.new(:unknown_statuses, :unknown_types, :unknown_priorities)
.new([nil], [nil], [nil])
end
let(:bcf_issue) { subject.extract! }
it 'sets a status' do
@ -160,7 +152,8 @@ describe ::OpenProject::Bcf::BcfXml::IssueReader do
context 'on updating import' do
context '#update_comment' do
let!(:bcf_issue) { FactoryBot.create :bcf_issue_with_comment }
let(:work_package) { FactoryBot.create(:work_package) }
let!(:bcf_issue) { FactoryBot.create :bcf_issue_with_comment, work_package: work_package }
before do
allow(subject).to receive(:issue).and_return(bcf_issue)

@ -84,16 +84,16 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
end
let(:bcf_issue) do
FactoryBot.create(:bcf_issue_with_comment,
work_package: work_package,
markup: markup)
end
let(:priority) { FactoryBot.create :priority_low }
let(:current_user) { FactoryBot.create(:user) }
let(:due_date) { DateTime.now }
let(:type) { FactoryBot.create :type, name: 'Issue'}
let(:type) { FactoryBot.create :type, name: 'Issue' }
let(:work_package) do
FactoryBot.create(:work_package,
project_id: project.id,
bcf_issue: bcf_issue,
priority: priority,
author: current_user,
assigned_to: current_user,
@ -104,7 +104,7 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
before do
allow(User).to receive(:current).and_return current_user
work_package.bcf_issue.comments.first.journal.update_attribute('journable_id', work_package.id)
bcf_issue.comments.first.journal.update_attribute('journable_id', work_package.id)
FactoryBot.create(:work_package_journal, notes: "Some note created in OP.", journable_id: work_package.id)
work_package.reload
end

@ -0,0 +1,45 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative './shared_contract_examples'
describe Bcf::Issues::CreateContract do
it_behaves_like 'issues contract' do
let(:issue) do
Bcf::Issue.new(uuid: issue_uuid,
work_package: issue_work_package,
stage: issue_stage,
index: issue_index,
labels: issue_labels)
end
let(:permissions) { [:manage_bcf] }
subject(:contract) { described_class.new(issue, current_user) }
end
end

@ -0,0 +1,129 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
shared_examples_for 'issues contract' do
let(:current_user) do
FactoryBot.build_stubbed(:user)
end
let!(:allowed_to) do
allow(current_user)
.to receive(:allowed_to?) do |permission, permission_project|
permissions.include?(permission) && project == permission_project
end
end
let(:issue_uuid) { 'issue uuid' }
let(:project) { FactoryBot.build_stubbed(:project) }
let(:issue_work_package) { FactoryBot.build_stubbed(:stubbed_work_package, project: project) }
let(:issue_work_package_id) do
id = 5
allow(WorkPackage)
.to receive(:find)
.with(id)
.and_return(issue_work_package)
id
end
let(:issue_stage) { nil }
let(:issue_labels) { [] }
let(:issue_index) { 8 }
before do
allow(issue)
.to receive(:project)
.and_return(project)
end
def expect_valid(valid, symbols = {})
expect(contract.validate).to eq(valid)
symbols.each do |key, arr|
expect(contract.errors.symbols_for(key)).to match_array arr
end
end
shared_examples 'is valid' do
it 'is valid' do
expect_valid(true)
end
end
it_behaves_like 'is valid'
context 'if the uuid is nil' do
let(:issue_uuid) { nil }
it_behaves_like 'is valid' # as the uuid will be set
end
context 'if the work_package_id is nil' do
let(:issue_work_package) { nil }
it 'is invalid' do
expect_valid(false, work_package: %i(blank))
end
end
context 'if the user lacks permission' do
let(:permissions) { [] }
it 'is invalid' do
expect_valid(false, base: %i(error_unauthorized))
end
end
context 'if the stage is nil' do
let(:issue_stage) { nil }
it_behaves_like 'is valid'
end
context 'if the stage is written' do
let(:issue_stage) { 'some stage' }
it 'is invalid' do
expect_valid(false, stage: %i(error_readonly))
end
end
context 'if labels is written' do
let(:issue_labels) { %w(some labels) }
it 'is invalid' do
expect_valid(false, labels: %i(error_readonly))
end
end
context 'if index is nil' do
let(:issue_index) { nil }
it_behaves_like 'is valid'
end
end

@ -1,20 +1,15 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject Backlogs Plugin
#
# Copyright (C)2013-2014 the OpenProject Foundation (OPF)
# Copyright (C)2011 Stephan Eckardt, Tim Felgentreff, Marnen Laibow-Koser, Sandro Munda
# Copyright (C)2010-2011 friflaj
# Copyright (C)2010 Maxime Guilbot, Andrew Vit, Joakim Kolsjö, ibussieres, Daniel Passos, Jason Vasquez, jpic, Emiliano Heyns
# Copyright (C)2009-2010 Mark Maglana
# Copyright (C)2009 Joe Heck, Nate Lowrie
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 3.
# 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 Backlogs is a derivative work based on ChiliProject Backlogs.
# The copyright follows:
# Copyright (C) 2010-2011 - Emiliano Heyns, Mark Maglana, friflaj
# Copyright (C) 2011 - Jens Ulferts, Gregor Schmidt - Finn GmbH - Berlin, Germany
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -30,7 +25,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
# See docs/COPYRIGHT.rdoc for more details.
#++
FactoryBot.define do
@ -114,6 +109,9 @@ FactoryBot.define do
</Markup>
MARKUP
end
stage { 'Construction stop' }
labels { ['Structural', 'IT Development', 'Huge'] }
sequence(:index) { |n| n }
factory :bcf_issue_with_viewpoint do
after(:create) do |issue|

@ -33,31 +33,6 @@ describe ::Bcf::Issue, type: :model do
let(:work_package) { FactoryBot.create :work_package, type: type }
let(:issue) { FactoryBot.create :bcf_issue, work_package: work_package }
shared_examples_for 'provides attributes' do
it "provides attributes" do
expect(subject.title).to be_eql 'Maximum Content'
expect(subject.description).to be_eql 'This is a topic with all information present.'
expect(subject.priority_text).to be_eql 'High'
expect(subject.status_text).to be_eql 'Open'
expect(subject.type_text).to be_eql 'Structural'
expect(subject.assignee_text).to be_eql 'andy@example.com'
expect(subject.index_text).to be_eql '0'
expect(subject.labels).to contain_exactly 'Structural', 'IT Development'
expect(subject.due_date_text).to be_nil
expect(subject.creation_date_text).to eql "2015-06-21T12:00:00Z"
expect(subject.creation_author_text).to eql "mike@example.com"
expect(subject.modified_date_text).to eql "2015-06-21T14:22:47Z"
expect(subject.modified_author_text).to eql "michelle@example.com"
expect(subject.stage_text).to eql "Construction start"
end
end
context '#self.with_markup' do
subject { ::Bcf::Issue.with_markup.find_by id: issue.id }
it_behaves_like 'provides attributes'
end
context '#markup_doc' do
subject { issue }
@ -76,8 +51,6 @@ describe ::Bcf::Issue, type: :model do
subject.save
expect(subject.markup_doc).to_not be_eql(first_fetched_doc)
end
it_behaves_like 'provides attributes'
end
describe '.of_project' do

@ -0,0 +1,135 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2019 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative '../shared_examples'
describe Bcf::API::V2_1::ProjectExtensions::Definitions, 'rendering' do
shared_let(:type_task) { FactoryBot.create :type_task, name: 'My BCF type' }
shared_let(:project) { FactoryBot.create(:project, types: [type_task]) }
let(:user) { FactoryBot.build_stubbed(:user) }
let(:instance) { described_class.new(project: project, user: user) }
describe '#topic_type' do
subject { instance.topic_type }
it 'returns the project type names' do
expect(subject).to eq ['My BCF type']
end
end
describe '#topic_status' do
let!(:default_status) { FactoryBot.create :default_status }
let!(:status) { FactoryBot.create :status }
subject { instance.topic_status }
it 'returns default status only' do
expect(subject).to eq [default_status.name]
end
end
describe '#priority' do
let!(:priority) { FactoryBot.create :default_priority }
subject { instance.priority }
it 'returns statuses for the available types' do
expect(subject).to eq [priority.name]
end
end
describe '#user_id_type' do
let!(:other_user) do
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: [:view_work_packages])
end
subject { instance.user_id_type }
before do
allow(user)
.to receive(:allowed_to?).with(:view_members, project)
.and_return is_permitted
end
context 'with permissions' do
let(:is_permitted) { true }
it 'returns the user as assignee' do
expect(subject).to eq [other_user.mail]
end
end
context 'with no permissions' do
let(:is_permitted) { false }
it 'returns nothing' do
expect(subject).to eq []
end
end
end
describe '#project_actions' do
subject { instance.project_actions }
it 'includes nothing if not permitted' do
allow(user).to receive(:allowed_to?).and_return false
expect(subject).to be_empty
end
it 'includes `update` if edit_project permission' do
allow(user).to receive(:allowed_to?).and_return false
allow(user).to receive(:allowed_to?).with(:edit_project, project).and_return true
expect(subject).to include 'update'
end
it 'includes `createTopic` if edit_project permission' do
allow(user).to receive(:allowed_to?).and_return false
allow(user).to receive(:allowed_to?).with(:manage_bcf, project).and_return true
expect(subject).to include 'createTopic'
end
end
describe '#topic_actions' do
subject { instance.topic_actions }
it 'includes nothing if not permitted' do
allow(user).to receive(:allowed_to?).and_return false
expect(subject).to be_empty
end
it 'includes `update` if manage_bcf permission' do
allow(user).to receive(:allowed_to?).and_return false
allow(user).to receive(:allowed_to?).with(:manage_bcf, project).and_return true
expect(subject).to match_array %w[update updateRelatedTopics updateFiles createViewpoint]
end
end
end

@ -33,7 +33,27 @@ require_relative '../shared_examples'
describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
include API::V3::Utilities::PathHelper
let(:work_package) { FactoryBot.build_stubbed(:stubbed_work_package, type: FactoryBot.build_stubbed(:type)) }
let(:assignee) { FactoryBot.build_stubbed(:user) }
let(:creator) { FactoryBot.build_stubbed(:user) }
let(:modifier) { FactoryBot.build_stubbed(:user) }
let(:first_journal) { FactoryBot.build_stubbed(:journal, version: 1, user: creator) }
let(:last_journal) { FactoryBot.build_stubbed(:journal, version: 2, user: modifier) }
let(:journals) { [first_journal, last_journal] }
let(:type) { FactoryBot.build_stubbed(:type) }
let(:status) { FactoryBot.build_stubbed(:status) }
let(:priority) { FactoryBot.build_stubbed(:priority) }
let(:work_package) do
FactoryBot.build_stubbed(:stubbed_work_package,
assigned_to: assignee,
due_date: Date.today,
status: status,
priority: priority,
type: type).tap do |wp|
allow(wp)
.to receive(:journals)
.and_return(journals)
end
end
let(:issue) { FactoryBot.build_stubbed(:bcf_issue, work_package: work_package) }
let(:instance) { described_class.new(issue) }
@ -50,18 +70,25 @@ describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
context 'topic_type' do
it_behaves_like 'attribute' do
let(:value) { issue.type_text }
let(:value) { type.name }
let(:path) { 'topic_type' }
end
end
context 'topic_status' do
it_behaves_like 'attribute' do
let(:value) { issue.status_text }
let(:value) { status.name }
let(:path) { 'topic_status' }
end
end
context 'priority' do
it_behaves_like 'attribute' do
let(:value) { priority.name }
let(:path) { 'priority' }
end
end
context 'reference_links' do
it_behaves_like 'attribute' do
let(:value) { [api_v3_paths.work_package(work_package.id)] }
@ -71,14 +98,14 @@ describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
context 'title' do
it_behaves_like 'attribute' do
let(:value) { issue.title }
let(:value) { work_package.subject }
let(:path) { 'title' }
end
end
context 'index' do
it_behaves_like 'attribute' do
let(:value) { issue.index_text }
let(:value) { issue.index }
let(:path) { 'index' }
end
end
@ -92,56 +119,56 @@ describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
context 'creation_date' do
it_behaves_like 'attribute' do
let(:value) { issue.creation_date_text }
let(:value) { work_package.created_at.iso8601 }
let(:path) { 'creation_date' }
end
end
context 'creation_author' do
it_behaves_like 'attribute' do
let(:value) { issue.creation_author_text }
let(:value) { work_package.author.mail }
let(:path) { 'creation_author' }
end
end
context 'modified_date' do
it_behaves_like 'attribute' do
let(:value) { issue.modified_date_text }
let(:value) { work_package.updated_at.iso8601 }
let(:path) { 'modified_date' }
end
end
context 'modified_author' do
it_behaves_like 'attribute' do
let(:value) { issue.modified_author_text }
let(:value) { modifier.mail }
let(:path) { 'modified_author' }
end
end
context 'description' do
it_behaves_like 'attribute' do
let(:value) { issue.description }
let(:value) { work_package.description }
let(:path) { 'description' }
end
end
context 'due_date' do
it_behaves_like 'attribute' do
let(:value) { issue.due_date_text }
let(:value) { work_package.due_date.iso8601 }
let(:path) { 'due_date' }
end
end
context 'assigned_to' do
it_behaves_like 'attribute' do
let(:value) { issue.assignee_text }
let(:value) { work_package.assigned_to.mail }
let(:path) { 'assigned_to' }
end
end
context 'stage' do
it_behaves_like 'attribute' do
let(:value) { issue.stage_text }
let(:value) { issue.stage }
let(:path) { 'stage' }
end
end

@ -0,0 +1,116 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2019 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
require_relative './shared_responses'
describe 'BCF 2.1 project extensions resource', type: :request, content_type: :json do
include Rack::Test::Methods
shared_let(:type_task) { FactoryBot.create :type_task }
shared_let(:status) { FactoryBot.create :default_status }
shared_let(:priority) { FactoryBot.create :default_priority }
shared_let(:project) { FactoryBot.create(:project, enabled_module_names: [:bcf], types: [type_task]) }
subject(:response) { last_response }
let(:path) { "/api/bcf/2.1/projects/#{project.id}/extensions" }
context 'with only view_project permissions' do
let(:current_user) do
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: [:view_project])
end
before do
login_as(current_user)
get path
end
it_behaves_like 'bcf api successful response' do
let(:expected_body) do
{
topic_type: [type_task.name],
topic_status: [status.name],
priority: [priority.name],
snippet_type: [],
stage: [],
topic_label: [],
user_id_type: [],
project_actions: [],
topic_actions: [],
comment_actions: []
}
end
end
end
context 'with edit permissions in project' do
let(:current_user) do
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: [:view_project, :edit_project, :manage_bcf, :view_members])
end
let(:other_user) {
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: [:view_project])
}
before do
other_user
login_as(current_user)
get path
end
it_behaves_like 'bcf api successful response expectation' do
let(:expectations) do
->(body) {
hash = JSON.parse(body)
expect(hash.keys).to match_array %w[
topic_type topic_status user_id_type project_actions topic_actions comment_actions
stage snippet_type priority topic_label
]
expect(hash['topic_type']).to include type_task.name
expect(hash['topic_status']).to include status.name
expect(hash['user_id_type']).to include(other_user.mail, current_user.mail)
expect(hash['project_actions']).to eq %w[update createTopic]
expect(hash['topic_actions']).to eq %w[update updateRelatedTopics updateFiles createViewpoint]
expect(hash['comment_actions']).to eq []
}
end
end
end
end

@ -119,7 +119,7 @@ describe 'BCF 2.1 projects resource', type: :request, content_type: :json do
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) { 'You must not write a read-only attribute.' }
let(:message) { 'ID was attempted to be written but is not writable.' }
end
end
end

@ -27,82 +27,57 @@
#++
shared_examples_for 'bcf api successful response' do
it 'responds 200 OK' do
it 'responds correctly with the expected body', :aggregate_failures do
expect(subject.status)
.to eql 200
.to eql(defined?(expected_status) ? expected_status : 200)
expect(subject.body).to be_json_eql(expected_body.to_json)
expect(subject.headers['Content-Type']).to eql 'application/json; charset=utf-8'
end
end
it 'returns the resource' do
expect(subject.body)
.to be_json_eql(expected_body.to_json)
end
shared_examples_for 'bcf api successful response expectation' do
it 'responds correctly with the expected body', :aggregate_failures do
expect(subject.status).to eq 200
instance_exec(subject.body, &expectations)
it 'is has a json content type header' do
expect(subject.headers['Content-Type'])
.to eql 'application/json; charset=utf-8'
expect(subject.headers['Content-Type']).to eql 'application/json; charset=utf-8'
end
end
shared_examples_for 'bcf api not found response' do
it 'responds 404 NOT FOUND' do
expect(subject.status)
.to eql 404
let(:expect_404) do
{ message: 'The requested resource could not be found.' }
end
it 'states a NOT FOUND message' do
expected = {
message: 'The requested resource could not be found.'
}
expect(subject.body)
.to be_json_eql(expected.to_json)
end
it 'is has a json content type header' do
expect(subject.headers['Content-Type'])
.to eql 'application/json; charset=utf-8'
it 'responds 404 NOT FOUND', :aggregate_failures do
expect(subject.status).to eq 404
expect(subject.body).to be_json_eql(expect_404.to_json)
expect(subject.headers['Content-Type']).to eql 'application/json; charset=utf-8'
end
end
shared_examples_for 'bcf api not allowed response' do
it 'responds 403 NOT ALLOWED' do
expect(subject.status)
.to eql 403
let(:expect_403) do
{ message: 'You are not authorized to access this resource.' }
end
it 'states a NOT ALLOWED message' do
expected = {
message: 'You are not authorized to access this resource.'
}
expect(subject.body)
.to be_json_eql(expected.to_json)
end
it 'is has a json content type header' do
expect(subject.headers['Content-Type'])
.to eql 'application/json; charset=utf-8'
it 'responds 403 NOT ALLOWED', :aggregate_failures do
expect(subject.status).to eq 403
expect(subject.body).to be_json_eql(expect_403.to_json)
expect(subject.headers['Content-Type']).to eql 'application/json; charset=utf-8'
end
end
shared_examples_for 'bcf api unprocessable response' do
it 'responds 403 NOT ALLOWED' do
expect(subject.status)
.to eql 422
let(:expect_422) do
{ message: message }
end
it 'states a reason message' do
expected = {
message: message
}
expect(subject.body)
.to be_json_eql(expected.to_json)
end
it 'is has a json content type header' do
expect(subject.headers['Content-Type'])
.to eql 'application/json; charset=utf-8'
it 'responds 422 UNPROCESSABLE ENTITY', :aggregate_failures do
expect(subject.status).to eq 422
expect(subject.body).to be_json_eql(expect_422.to_json)
expect(subject.headers['Content-Type']).to eql 'application/json; charset=utf-8'
end
end

@ -38,22 +38,32 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
let(:view_only_user) do
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: [:view_linked_issues])
member_with_permissions: %i[view_linked_issues view_work_packages])
end
let(:only_member_user) do
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: [])
end
let(:edit_member_user) do
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: %i[manage_bcf add_work_packages view_linked_issues])
end
let(:non_member_user) do
FactoryBot.create(:user)
end
let(:project) do
FactoryBot.create(:project,
enabled_module_names: [:bcf])
enabled_module_names: %i[bcf work_package_tracking])
end
let(:assignee) { FactoryBot.create(:user) }
let(:work_package) do
FactoryBot.create(:work_package,
assigned_to: assignee,
project: project)
end
let(:work_package) { FactoryBot.create(:work_package, project: project) }
let(:bcf_issue) { FactoryBot.create(:bcf_issue, work_package: work_package) }
subject(:response) { last_response }
@ -72,26 +82,24 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
let(:expected_body) do
[
{
"assigned_to": "andy@example.com",
"creation_author": "mike@example.com",
"creation_date": "2015-06-21T12:00:00Z",
"description": "This is a topic with all information present.",
"assigned_to": assignee.mail,
"creation_author": work_package.author.mail,
"creation_date": work_package.created_at.iso8601,
"description": work_package.description,
"due_date": nil,
guid: bcf_issue.uuid,
"index": "0",
"labels": [
"Structural",
"IT Development"
],
"modified_author": "michelle@example.com",
"modified_date": "2015-06-21T14:22:47Z",
"guid": bcf_issue.uuid,
"index": bcf_issue.index,
"labels": bcf_issue.labels,
"priority": work_package.priority.name,
"modified_author": current_user.mail,
"modified_date": work_package.updated_at.iso8601,
"reference_links": [
api_v3_paths.work_package(work_package.id)
],
"stage": "Construction start",
"title": "Maximum Content",
"topic_status": "Open",
"topic_type": "Structural"
"stage": bcf_issue.stage,
"title": work_package.subject,
"topic_status": work_package.status.name,
"topic_type": work_package.type.name
}
]
end
@ -123,26 +131,24 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
it_behaves_like 'bcf api successful response' do
let(:expected_body) do
{
"assigned_to": "andy@example.com",
"creation_author": "mike@example.com",
"creation_date": "2015-06-21T12:00:00Z",
"description": "This is a topic with all information present.",
"assigned_to": assignee.mail,
"creation_author": work_package.author.mail,
"creation_date": work_package.created_at.iso8601,
"description": work_package.description,
"due_date": nil,
guid: bcf_issue.uuid,
"index": "0",
"labels": [
"Structural",
"IT Development"
],
"modified_author": "michelle@example.com",
"modified_date": "2015-06-21T14:22:47Z",
"guid": bcf_issue.uuid,
"index": bcf_issue.index,
"labels": bcf_issue.labels,
"priority": work_package.priority.name,
"modified_author": current_user.mail,
"modified_date": work_package.updated_at.iso8601,
"reference_links": [
api_v3_paths.work_package(work_package.id)
],
"stage": "Construction start",
"title": "Maximum Content",
"topic_status": "Open",
"topic_type": "Structural"
"stage": bcf_issue.stage,
"title": work_package.subject,
"topic_status": work_package.status.name,
"topic_type": work_package.type.name
}
end
end
@ -165,4 +171,240 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
it_behaves_like 'bcf api not allowed response'
end
end
describe 'POST /api/bcf/2.1/projects/:project_id/topics' do
let(:path) { "/api/bcf/2.1/projects/#{project.id}/topics" }
let(:current_user) { edit_member_user }
let(:type) do
FactoryBot.create(:type).tap do |t|
project.types << t
end
end
let(:status) do
FactoryBot.create(:status)
end
let!(:default_status) do
FactoryBot.create(:default_status)
end
let!(:default_type) do
FactoryBot.create(:type, is_default: true)
end
let!(:standard_type) do
FactoryBot.create(:type_standard)
end
let!(:priority) do
FactoryBot.create(:priority)
end
let!(:default_priority) do
FactoryBot.create(:default_priority)
end
let(:description) { 'some description' }
let(:stage) { nil }
let(:labels) { [] }
let(:index) { 5 }
let(:params) do
{
topic_type: type.name,
topic_status: status.name,
priority: priority.name,
title: 'BCF topic 101',
labels: labels,
stage: stage,
index: index,
due_date: Date.today.iso8601,
assigned_to: view_only_user.mail,
description: description
}
end
before do
login_as(current_user)
post path, params.to_json
end
it_behaves_like 'bcf api successful response' do
let(:expected_status) { 201 }
let(:expected_body) do
issue = Bcf::Issue.last
work_package = WorkPackage.last
{
guid: issue&.uuid,
topic_type: type.name,
topic_status: status.name,
priority: priority.name,
title: 'BCF topic 101',
labels: labels,
index: index,
reference_links: [
api_v3_paths.work_package(work_package&.id)
],
assigned_to: view_only_user.mail,
due_date: Date.today.iso8601,
stage: stage,
creation_author: edit_member_user.mail,
creation_date: work_package&.created_at&.iso8601,
modified_author: edit_member_user.mail,
modified_date: work_package&.updated_at&.iso8601,
description: description
}
end
end
context 'with minimal parameters' do
let(:params) do
{
title: 'BCF topic 101'
}
end
it_behaves_like 'bcf api successful response' do
let(:expected_status) { 201 }
let(:expected_body) do
issue = Bcf::Issue.last
work_package = WorkPackage.last
{
guid: issue&.uuid,
topic_type: standard_type.name,
topic_status: default_status.name,
priority: default_priority.name,
title: 'BCF topic 101',
labels: [],
index: nil,
reference_links: [
api_v3_paths.work_package(work_package&.id)
],
assigned_to: nil,
due_date: nil,
stage: nil,
creation_author: edit_member_user.mail,
creation_date: work_package&.created_at&.iso8601,
modified_author: edit_member_user.mail,
modified_date: work_package&.updated_at&.iso8601,
description: nil
}
end
end
end
context 'without a title' do
let(:params) do
{
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Title can't be blank."
end
end
end
context 'with an inexistent status' do
let(:params) do
{
title: 'Some title',
topic_status: 'Some non existing status'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Status does not exist."
end
end
end
context 'with an inexistent priority' do
let(:params) do
{
title: 'Some title',
priority: 'Some non existing priority'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Priority does not exist."
end
end
end
context 'with an inexistent type' do
let(:params) do
{
title: 'Some title',
topic_type: 'Some non existing type'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Type does not exist."
end
end
end
context 'with an inexistent assigned_to' do
let(:params) do
{
title: 'Some title',
assigned_to: 'Some non existing assignee'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Assignee does not exist."
end
end
end
context 'with two inexistent related resources' do
let(:params) do
{
title: 'Some title',
assigned_to: 'Some non existing assignee',
topic_type: 'Some non existing type'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Multiple field constraints have been violated. Type does not exist. Assignee does not exist."
end
end
end
context 'with a label' do
let(:params) do
{
title: 'Some title',
labels: ['some label']
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Labels was attempted to be written but is not writable."
end
end
end
context 'with a stage' do
let(:params) do
{
title: 'Some title',
stage: 'some stage'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Stage was attempted to be written but is not writable."
end
end
end
end
end

@ -0,0 +1,173 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe Bcf::Issues::CreateService, type: :model do
let(:user) { FactoryBot.build_stubbed(:user) }
let(:contract_class) do
double('contract_class', '<=': true)
end
let(:issue_valid) { true }
let(:instance) do
described_class.new(user: user,
contract_class: contract_class)
end
let(:call_attributes) { { subject: 'Some name' } }
let(:set_attributes_success) do
true
end
let(:set_attributes_errors) do
double('set_attributes_errors')
end
let(:set_attributes_result) do
ServiceResult.new result: created_issue,
success: set_attributes_success,
errors: set_attributes_errors
end
let!(:created_work_package) do
FactoryBot.build_stubbed(:work_package)
end
let(:wp_create_errors) do
double('wp_create_errors')
end
let(:wp_create_result) do
ServiceResult.new result: created_work_package,
success: true,
errors: wp_create_errors
end
let!(:wp_create_service) do
wp_service = double('wp create service')
allow(WorkPackages::CreateService)
.to receive(:new)
.with(user: user)
.and_return(wp_service)
allow(wp_service)
.to receive(:call)
.and_return(wp_create_result)
wp_service
end
let!(:created_issue) do
issue = FactoryBot.build_stubbed(:bcf_issue)
allow(Bcf::Issue)
.to receive(:new)
.and_return(issue)
allow(issue)
.to receive(:save)
.and_return(issue_valid)
issue
end
let!(:set_attributes_service) do
service = double('set_attributes_service_instance')
allow(Bcf::Issues::SetAttributesService)
.to receive(:new)
.with(user: user,
model: created_issue,
contract_class: contract_class)
.and_return(service)
allow(service)
.to receive(:call)
.and_return(set_attributes_result)
end
describe '#call' do
subject { instance.call(call_attributes) }
it 'is successful' do
expect(subject.success?).to be_truthy
end
it 'returns the result of the SetAttributesService' do
expect(subject)
.to eql set_attributes_result
end
it 'persists the issue' do
expect(created_issue)
.to receive(:save)
.and_return(issue_valid)
subject
end
it 'creates a issue' do
expect(subject.result)
.to eql created_issue
end
context 'if the SetAttributeService is unsuccessful' do
let(:set_attributes_success) { false }
it 'is unsuccessful' do
expect(subject.success?).to be_falsey
end
it 'returns the result of the SetAttributesService' do
expect(subject)
.to eql set_attributes_result
end
it 'does not persist the changes' do
expect(created_issue)
.to_not receive(:save)
subject
end
it "exposes the contract's errors" do
subject
expect(subject.errors).to eql set_attributes_errors
end
end
context 'when the issue is invalid' do
let(:issue_valid) { false }
it 'is unsuccessful' do
expect(subject.success?).to be_falsey
end
it "exposes the issue's errors" do
subject
expect(subject.errors).to eql created_issue.errors
end
end
end
end

@ -288,21 +288,7 @@ describe 'API v3 Grids resource for Board Grids', type: :request, content_type:
}.with_indifferent_access
end
it 'responds with 422 and mentions the error' do
expect(subject.status).to eq 422
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.at_path('message')
expect(subject.body)
.to be_json_eql("scope".to_json)
.at_path('_embedded/details/attribute')
end
it_behaves_like 'read-only violation', 'scope', Boards::Grid
end
context 'with the grid not existing' do

@ -121,7 +121,7 @@ describe "PATCH /api/v3/grids/:id/form for Board Grids", type: :request, content
it 'has a validation error on scope as the value is not writeable' do
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.to be_json_eql("Scope was attempted to be written but is not writable.".to_json)
.at_path('_embedded/validationErrors/scope/message')
end
end

@ -1,5 +1,5 @@
tr:
dashboards:
label: 'Gösterge tabloları'
label: 'Gösterge panosu'
menu_badge: 'Alfa'
project_module_dashboards: 'Gösterge tabloları'
project_module_dashboards: 'Gösterge panosu'

@ -41,38 +41,30 @@ module API
end
schema :id,
type: 'Integer',
visibility: false
type: 'Integer'
schema :created_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
schema :updated_at,
type: 'DateTime',
visibility: false
type: 'DateTime'
schema :row_count,
type: 'Integer',
visibility: false
type: 'Integer'
schema :column_count,
type: 'Integer',
visibility: false
type: 'Integer'
schema :name,
type: 'String',
visibility: false
type: 'String'
schema :options,
type: 'JSON',
visibility: false
type: 'JSON'
schema_with_allowed_collection :scope,
type: 'Href',
required: true,
has_default: false,
visibility: false,
value_representer: false,
link_factory: ->(path) {
{
@ -84,7 +76,6 @@ module API
type: '[]GridWidget',
required: true,
has_default: false,
visibility: false,
values_callback: -> do
represented.assignable_widgets.map do |identifier|
OpenStruct.new(identifier: identifier, grid: represented.model, options: {})

@ -94,8 +94,6 @@ describe ::API::V3::Grids::Schemas::GridSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'rowCount' do
@ -107,8 +105,6 @@ describe ::API::V3::Grids::Schemas::GridSchemaRepresenter do
let(:required) { true }
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'columnCount' do
@ -120,8 +116,6 @@ describe ::API::V3::Grids::Schemas::GridSchemaRepresenter do
let(:required) { true }
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'createdAt' do
@ -133,8 +127,6 @@ describe ::API::V3::Grids::Schemas::GridSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'updatedAt' do
@ -146,8 +138,6 @@ describe ::API::V3::Grids::Schemas::GridSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'widgets' do

@ -277,21 +277,7 @@ describe 'API v3 Grids resource', type: :request, content_type: :json do
}.with_indifferent_access
end
it 'responds with 422 and mentions the error' do
expect(subject.status).to eq 422
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.at_path('message')
expect(subject.body)
.to be_json_eql("scope".to_json)
.at_path('_embedded/details/attribute')
end
it_behaves_like 'read-only violation', 'scope', Grids::Grid
end
context 'with the page not existing' do

@ -132,7 +132,7 @@ describe "PATCH /api/v3/grids/:id/form", type: :request, content_type: :json do
it 'has a validation error on scope as the value is not writeable' do
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.to be_json_eql("Scope was attempted to be written but is not writable.".to_json)
.at_path('_embedded/validationErrors/scope/message')
end
end

@ -174,7 +174,7 @@ describe WorkPackages::BaseContract do
before do
allow(work_package)
.to receive(:status_id_change)
.and_return [1,2]
.and_return [1, 2]
end
it 'is writable' do
@ -182,6 +182,19 @@ describe WorkPackages::BaseContract do
end
end
end
context 'is an inexistent status' do
before do
work_package.status = Status::InexistentStatus.new
end
it 'is invalid' do
contract.validate
expect(subject.errors.symbols_for(:status))
.to match_array [:does_not_exist]
end
end
end
describe 'estimated hours' do
@ -521,6 +534,34 @@ describe WorkPackages::BaseContract do
end
end
end
context 'inexistent type' do
before do
work_package.type = Type::InexistentType.new
contract.validate
end
it 'is invalid' do
expect(contract.errors.symbols_for(:type))
.to match_array [:does_not_exist]
end
end
end
context 'assigned_to' do
context 'inexistent user' do
before do
work_package.assigned_to = User::InexistentUser.new
contract.validate
end
it 'is invalid' do
expect(contract.errors.symbols_for(:assigned_to))
.to match_array [:does_not_exist]
end
end
end
describe 'category' do
@ -630,6 +671,19 @@ describe WorkPackages::BaseContract do
.to be_empty
end
end
context 'inexistent priority' do
before do
work_package.priority = Priority::InexistentPriority.new
contract.validate
end
it 'is invalid' do
expect(contract.errors.symbols_for(:priority))
.to match_array [:does_not_exist]
end
end
end
describe 'status' do

@ -28,7 +28,7 @@
require 'spec_helper'
describe 'Work package timeline navigation',
describe 'Switching work package view',
with_ee: %i[conditional_highlighting],
js: true do
let(:user) { FactoryBot.create(:admin) }
@ -69,8 +69,7 @@ describe 'Work package timeline navigation',
before do
# Enable card representation
display_representation.switch_to_card_layout
expect(page).to have_selector("wp-single-card[data-work-package-id='#{wp_1.id}']")
expect(page).to have_selector("wp-single-card[data-work-package-id='#{wp_2.id}']")
cards.expect_work_package_listed wp_1, wp_2
end
it 'can switch the representations and keep the configuration settings' do
@ -107,8 +106,48 @@ describe 'Work package timeline navigation',
it 'saves the representation in the query' do
# After refresh the WP are still disaplyed as cards
page.driver.browser.navigate.refresh
expect(page).to have_selector("wp-single-card[data-work-package-id='#{wp_1.id}']")
expect(page).to have_selector("wp-single-card[data-work-package-id='#{wp_2.id}']")
cards.expect_work_package_listed wp_1, wp_2
end
end
context 'switching to mobile card view' do
let!(:height_before) do
page.driver.browser.manage.window.size.height
end
let!(:width_before) do
page.driver.browser.manage.window.size.width
end
after do
page.driver.browser.manage.window.resize_to(width_before, height_before)
end
it 'can switch the representation automatically on mobile after a refresh' do
# Change browser size to mobile
page.driver.browser.manage.window.resize_to(679, 1080)
# Expect the representation to switch to card on mobile
page.driver.browser.navigate.refresh
# It shows the elements as cards
cards.expect_work_package_listed wp_1, wp_2
# A single click leads to the full view
cards.select_work_package(wp_1)
expect(page).to have_selector('.work-packages--details--subject',
text: wp_1.subject)
page.find('.work-packages-back-button').click
# The query is however unchanged
expect(page).not_to have_selector('.editable-toolbar-title--save')
url = URI.parse(page.current_url).query
expect(url).not_to match(/query_props=.+/)
# Since the query is unchanged, the WPs will be displayed as list on larger screens again
page.driver.browser.manage.window.resize_to(680, 1080)
page.driver.browser.navigate.refresh
wp_table.expect_work_package_listed wp_1, wp_2
wp_table.expect_work_package_order wp_1, wp_2
end
end

@ -96,8 +96,6 @@ describe ::API::V3::Memberships::Schemas::MembershipSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'createdAt' do
@ -109,8 +107,6 @@ describe ::API::V3::Memberships::Schemas::MembershipSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'project' do

@ -100,8 +100,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'name' do
@ -118,8 +116,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:min_length) { 1 }
let(:max_length) { 255 }
end
it_behaves_like 'has no visibility property'
end
describe 'identifier' do
@ -136,8 +132,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:min_length) { 1 }
let(:max_length) { 100 }
end
it_behaves_like 'has no visibility property'
end
describe 'description' do
@ -149,8 +143,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:required) { false }
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'public' do
@ -162,8 +154,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:required) { true }
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'active' do
@ -175,8 +165,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:required) { true }
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'statusExplanation' do
@ -222,8 +210,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'updatedAt' do
@ -235,8 +221,6 @@ describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'int custom field' do

@ -105,8 +105,6 @@ describe ::API::V3::Queries::Schemas::QueryFilterInstanceSchemaRepresenter, clea
let(:writable) { false }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'filter' do
@ -119,8 +117,6 @@ describe ::API::V3::Queries::Schemas::QueryFilterInstanceSchemaRepresenter, clea
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do
@ -150,8 +146,6 @@ describe ::API::V3::Queries::Schemas::QueryFilterInstanceSchemaRepresenter, clea
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do

@ -112,8 +112,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'name' do
@ -130,8 +128,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:min_length) { 1 }
let(:max_length) { 255 }
end
it_behaves_like 'has no visibility property'
end
describe 'createdAt' do
@ -143,8 +139,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'updatedAt' do
@ -156,8 +150,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'user' do
@ -170,8 +162,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { false }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'project' do
@ -184,8 +174,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do
@ -208,8 +196,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
context 'when having the :manage_public_queries permission' do
before do
allow(user)
@ -248,8 +234,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'timelineVisible' do
@ -262,8 +246,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'timelineZoomLevel' do
@ -276,8 +258,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'timelineLabels' do
@ -290,8 +270,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'show hierarchies' do
@ -304,8 +282,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'starred' do
@ -318,8 +294,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { false }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'highlighting_mode' do
@ -332,8 +306,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'display_representation' do
@ -346,8 +318,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'columns' do
@ -361,8 +331,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do
@ -441,8 +409,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when global query' do
@ -477,8 +443,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do
@ -511,8 +475,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:has_default) { true }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do
@ -553,8 +515,6 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:required) { false }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
end

@ -34,8 +34,6 @@ shared_examples_for 'filter dependency' do
let(:has_default) { false }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do
@ -53,8 +51,6 @@ shared_examples_for 'filter dependency with allowed link' do
let(:has_default) { false }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do
@ -72,8 +68,6 @@ shared_examples_for 'filter dependency with allowed value link collection' do
let(:has_default) { false }
end
it_behaves_like 'has no visibility property'
it_behaves_like 'does not link to allowed values'
context 'when embedding' do

@ -86,13 +86,6 @@ shared_examples_for 'indicates length requirements' do
end
end
shared_examples_for 'has no visibility property' do
it 'has no path' do
is_expected
.not_to have_json_path("#{path}/visibility")
end
end
shared_examples_for 'links to allowed values directly' do
it 'has the expected number of links' do
is_expected.to have_json_size(hrefs.size).at_path("#{path}/_links/allowedValues")

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

Loading…
Cancel
Save