diff --git a/app/models/custom_option.rb b/app/models/custom_option.rb
index 64e3a2991d..d33921c60a 100644
--- a/app/models/custom_option.rb
+++ b/app/models/custom_option.rb
@@ -36,4 +36,10 @@ class CustomOption < ActiveRecord::Base
belongs_to :custom_field
validates :value, presence: true, length: { maximum: 255 }
+
+ def to_s
+ value
+ end
+
+ alias :name :to_s
end
diff --git a/app/models/queries/work_packages/filter/custom_field_filter.rb b/app/models/queries/work_packages/filter/custom_field_filter.rb
index c501007335..dd7b62d342 100644
--- a/app/models/queries/work_packages/filter/custom_field_filter.rb
+++ b/app/models/queries/work_packages/filter/custom_field_filter.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -119,7 +120,7 @@ class Queries::WorkPackages::Filter::CustomFieldFilter <
when 'version'
Version.find(values)
when 'list'
- value_objects_for_list
+ custom_field.custom_options.find(values)
else
super
end
@@ -182,16 +183,6 @@ class Queries::WorkPackages::Filter::CustomFieldFilter <
.map(&:id).include? custom_field.id
end
- def value_objects_for_list
- objects = allowed_values.select do |value|
- values.include? value.last.to_s
- end
-
- objects.map do |value|
- Queries::StringObject.new(value.last, value.first)
- end
- end
-
def strategies
strategies = Queries::Filters::STRATEGIES.dup
strategies[:list_optional] = Queries::Filters::Strategies::CfListOptional
@@ -200,17 +191,3 @@ class Queries::WorkPackages::Filter::CustomFieldFilter <
strategies
end
end
-
-#
-# This object is only used to transport the values tothe query filter instance
-# representer which expects a class and deduces the path from the classes' name.
-#
-class Queries::StringObject
- attr_accessor :id,
- :name
-
- def initialize(id, name)
- self.id = [name, id]
- self.name = name
- end
-end
diff --git a/app/models/query/results.rb b/app/models/query/results.rb
index f35d66b4fe..8ef470ca72 100644
--- a/app/models/query/results.rb
+++ b/app/models/query/results.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -56,22 +57,9 @@ class ::Query::Results
@work_package_count_by_group ||= begin
r = nil
if query.grouped?
- begin
- # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
- r = WorkPackage
- .group(query.group_by_statement)
- .visible
- .includes(:status, :project)
- .where(query.statement)
- .references(:statuses, :projects)
- .count
- rescue ActiveRecord::RecordNotFound
- r = { nil => work_package_count }
- end
- c = query.group_by_column
- if c.is_a?(QueryCustomFieldColumn)
- r = r.keys.inject({}) { |h, k| h[c.custom_field.cast_value(k)] = r[k]; h }
- end
+ r = groups_grouped_by_column(query)
+
+ r = transform_group_keys(query, r)
end
r
end
@@ -164,4 +152,52 @@ class ::Query::Results
def custom_field_column?(name)
name.to_s =~ /\Acf_\d+\z/
end
+
+ def groups_grouped_by_column(query)
+ # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
+ WorkPackage
+ .group(query.group_by_statement)
+ .visible
+ .includes(:status, :project)
+ .where(query.statement)
+ .references(:statuses, :projects)
+ .count
+ rescue ActiveRecord::RecordNotFound
+ { nil => work_package_count }
+ end
+
+ def transform_group_keys(query, groups)
+ column = query.group_by_column
+
+ if column.is_a?(QueryCustomFieldColumn) && column.custom_field.list?
+ transform_list_group_by_keys(column.custom_field, groups)
+ elsif column.is_a?(QueryCustomFieldColumn)
+ transform_custom_field_keys(column.custom_field, groups)
+ else
+ groups
+ end
+ end
+
+ def transform_list_group_by_keys(custom_field, groups)
+ options = custom_options_for_keys(custom_field, groups)
+
+ groups.transform_keys do |key|
+ if custom_field.multi_value?
+ key.split('.').map do |subkey|
+ options[subkey].first
+ end
+ else
+ options[key] ? options[key].first : nil
+ end
+ end
+ end
+
+ def custom_options_for_keys(custom_field, groups)
+ keys = groups.keys.map { |k| k.split('.') }
+ custom_field.custom_options.find(keys.flatten).group_by { |o| o.id.to_s }
+ end
+
+ def transform_custom_field_keys(custom_field, groups)
+ groups.transform_keys { |key| custom_field.cast_value(key) }
+ end
end
diff --git a/docs/api/apiv3-documentation.apib b/docs/api/apiv3-documentation.apib
index 8dfd7b725b..297027d474 100644
--- a/docs/api/apiv3-documentation.apib
+++ b/docs/api/apiv3-documentation.apib
@@ -10,6 +10,7 @@ FORMAT: 1A
+
diff --git a/docs/api/apiv3/endpoints/custom-options.apib b/docs/api/apiv3/endpoints/custom-options.apib
new file mode 100644
index 0000000000..f03fdb572e
--- /dev/null
+++ b/docs/api/apiv3/endpoints/custom-options.apib
@@ -0,0 +1,51 @@
+# Group Custom Objects
+
+## Linked Properties
+| Link | Description | Type | Constraints | Supported operations |
+|:-------------:|-------------------------- | ------------- | ----------- | -------------------- |
+| self | This custom object | CustomObject | not null | READ |
+
+## Local Properties
+| Property | Description | Type | Constraints | Supported operations |
+|:----------------:| ---------------------------------------------- | -------- | ----------- | -------------------- |
+| id | The identifier | Integer | | READ |
+| value | The value defined for this custom object | String | | READ |
+
+
+Custom objects are options of list custom fields.
+
+## Custom Object [/api/v3/custom_objects/{id}]
+
++ Model
+ + Body
+
+ {
+ "_links": {
+ "self": { "href": "/api/v3/custom_objects/1" }
+ },
+ "_type": "CustomObject",
+ "value": "Foo"
+ }
+
+## View Custom Object [GET]
+
++ Parameters
+ + id (required, integer, `1`) ... The custom object's identifier
+
++ Response 200 (application/hal+json)
+
+ [Custom Object][]
+
++ Response 404 (application/hal+json)
+
+ Returned if the custom object does not exist or the client does not have sufficient permissions to see it.
+
+ **Required permission:** view work package in any project the custom object's custom field is active in.
+
+ + Body
+
+ {
+ "_type": "Error",
+ "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
+ "message": "The specified resource does not exist."
+ }
diff --git a/doc/apiv3/endpoints/principals.apib b/docs/api/apiv3/endpoints/principals.apib
similarity index 100%
rename from doc/apiv3/endpoints/principals.apib
rename to docs/api/apiv3/endpoints/principals.apib
diff --git a/doc/apiv3/endpoints/roles.apib b/docs/api/apiv3/endpoints/roles.apib
similarity index 100%
rename from doc/apiv3/endpoints/roles.apib
rename to docs/api/apiv3/endpoints/roles.apib
diff --git a/frontend/app/components/work-packages/wp-display-attr/wp-display-attr.directive.ts b/frontend/app/components/work-packages/wp-display-attr/wp-display-attr.directive.ts
index 77045ae1f4..d00202ef40 100644
--- a/frontend/app/components/work-packages/wp-display-attr/wp-display-attr.directive.ts
+++ b/frontend/app/components/work-packages/wp-display-attr/wp-display-attr.directive.ts
@@ -109,7 +109,8 @@ export class WorkPackageDisplayAttributeController {
protected updateAttribute(wp:WorkPackageResourceInterface) {
this.workPackage = wp;
- if (this.schema[this.attribute] && (this.schema[this.attribute].type === '[]StringObject' || this.schema[this.attribute].type === '[]User')) {
+ if (this.schema[this.attribute] && (this.schema[this.attribute].type === '[]CustomOption' ||
+ this.schema[this.attribute].type === '[]User')) {
this.field = new MultipleLinesStringObjectsDisplayField(this.workPackage, this.attribute, this.schema[this.attribute])
} else {
this.field = this.wpDisplayField.getField(this.workPackage, this.attribute, this.schema[this.attribute]) as DisplayField;
diff --git a/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.directive.html b/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.directive.html
index 6fc2829424..be0a1fabdf 100644
--- a/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.directive.html
+++ b/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.directive.html
@@ -6,4 +6,4 @@
-
-
\ No newline at end of file
+
diff --git a/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.module.ts b/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.module.ts
index 047a8f0960..b08267a6cf 100644
--- a/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.module.ts
+++ b/frontend/app/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.module.ts
@@ -26,8 +26,8 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
-import {StringObjectsDisplayField} from "./wp-display-string-objects-field.module";
+import {ResourcesDisplayField} from "./wp-display-resources-field.module";
-export class MultipleLinesStringObjectsDisplayField extends StringObjectsDisplayField {
+export class MultipleLinesStringObjectsDisplayField extends ResourcesDisplayField {
public template: string = '/components/wp-display/field-types/wp-display-multiple-lines-string-objects-field.directive.html';
}
diff --git a/frontend/app/components/wp-display/field-types/wp-display-resources-field-multiline.directive.html b/frontend/app/components/wp-display/field-types/wp-display-resources-field-multiline.directive.html
new file mode 100644
index 0000000000..7d246cb95a
--- /dev/null
+++ b/frontend/app/components/wp-display/field-types/wp-display-resources-field-multiline.directive.html
@@ -0,0 +1,10 @@
+
+
+ {{ value }}
+
+
+
+ -
+
+
+
diff --git a/frontend/app/components/wp-display/field-types/wp-display-string-objects-field.directive.html b/frontend/app/components/wp-display/field-types/wp-display-resources-field.directive.html
similarity index 100%
rename from frontend/app/components/wp-display/field-types/wp-display-string-objects-field.directive.html
rename to frontend/app/components/wp-display/field-types/wp-display-resources-field.directive.html
diff --git a/frontend/app/components/wp-display/field-types/wp-display-string-objects-field.module.ts b/frontend/app/components/wp-display/field-types/wp-display-resources-field.module.ts
similarity index 94%
rename from frontend/app/components/wp-display/field-types/wp-display-string-objects-field.module.ts
rename to frontend/app/components/wp-display/field-types/wp-display-resources-field.module.ts
index 69a683c661..87d8acb624 100644
--- a/frontend/app/components/wp-display/field-types/wp-display-string-objects-field.module.ts
+++ b/frontend/app/components/wp-display/field-types/wp-display-resources-field.module.ts
@@ -28,8 +28,8 @@
import {DisplayField} from "../wp-display-field/wp-display-field.module";
-export class StringObjectsDisplayField extends DisplayField {
- public template: string = '/components/wp-display/field-types/wp-display-string-objects-field.directive.html';
+export class ResourcesDisplayField extends DisplayField {
+ public template: string = '/components/wp-display/field-types/wp-display-resources-field.directive.html';
public get value() {
if (this.schema) {
diff --git a/frontend/app/components/wp-display/field-types/wp-display-string-object-field.module.ts b/frontend/app/components/wp-display/field-types/wp-display-string-object-field.module.ts
deleted file mode 100644
index 88bc90a606..0000000000
--- a/frontend/app/components/wp-display/field-types/wp-display-string-object-field.module.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-// -- copyright
-// OpenProject is a project management system.
-// Copyright (C) 2012-2015 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-2013 Jean-Philippe Lang
-// Copyright (C) 2010-2013 the ChiliProject Team
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-//
-// See doc/COPYRIGHT.rdoc for more details.
-// ++
-
-import {DisplayField} from "../wp-display-field/wp-display-field.module";
-
-export class StringObjectDisplayField extends DisplayField {
- public get value() {
- if(this.schema) {
- return this.resource[this.name] && (this.resource[this.name].value || this.resource[this.name].name);
- }
- else {
- return null;
- }
- }
-}
diff --git a/frontend/app/components/wp-display/wp-display-field/wp-display-field.config.ts b/frontend/app/components/wp-display/wp-display-field/wp-display-field.config.ts
index 1e1c6f758a..f2e74d6d0a 100644
--- a/frontend/app/components/wp-display/wp-display-field/wp-display-field.config.ts
+++ b/frontend/app/components/wp-display/wp-display-field/wp-display-field.config.ts
@@ -29,8 +29,7 @@
import {WorkPackageDisplayFieldService} from './wp-display-field.service';
import {TextDisplayField} from '../field-types/wp-display-text-field.module';
import {ResourceDisplayField} from '../field-types/wp-display-resource-field.module';
-import {StringObjectDisplayField} from '../field-types/wp-display-string-object-field.module';
-import {StringObjectsDisplayField} from '../field-types/wp-display-string-objects-field.module';
+import {ResourcesDisplayField} from '../field-types/wp-display-resources-field.module';
import {FormattableDisplayField} from '../field-types/wp-display-formattable-field.module';
import {DurationDisplayField} from '../field-types/wp-display-duration-field.module';
import {DateDisplayField} from '../field-types/wp-display-date-field.module';
@@ -55,10 +54,10 @@ openprojectModule
'Status',
'Priority',
'Version',
- 'Category'])
- .addFieldType(StringObjectDisplayField, 'string_object', ['StringObject'])
- .addFieldType(StringObjectsDisplayField, 'string_objects', ['[]StringObject'])
- .addFieldType(StringObjectsDisplayField, 'users', ['[]User'])
+ 'Category',
+ 'CustomOption'])
+ .addFieldType(ResourcesDisplayField, 'resources', ['[]CustomOption',
+ '[]User'])
.addFieldType(FormattableDisplayField, 'formattable', ['Formattable'])
.addFieldType(DurationDisplayField, 'duration', ['Duration'])
.addFieldType(DateDisplayField, 'date', ['Date'])
diff --git a/frontend/app/components/wp-display/wp-display-field/wp-display-field.module.ts b/frontend/app/components/wp-display/wp-display-field/wp-display-field.module.ts
index 96c544d490..0f6cffb0e4 100644
--- a/frontend/app/components/wp-display/wp-display-field/wp-display-field.module.ts
+++ b/frontend/app/components/wp-display/wp-display-field/wp-display-field.module.ts
@@ -36,6 +36,7 @@ export class DisplayField extends Field {
public static $injector: ng.auto.IInjectorService;
public template:string|null = null;
public I18n: op.I18n;
+ public mode:string|null = null;
public get value() {
if (this.schema) {
diff --git a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.config.ts b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.config.ts
index e8f0e6043d..80df216675 100644
--- a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.config.ts
+++ b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.config.ts
@@ -39,13 +39,6 @@ import {DateEditField} from "../field-types/wp-edit-date-field.module";
import {WikiTextareaEditField} from "../field-types/wp-edit-wiki-textarea-field.module";
import {openprojectModule} from "../../../angular-modules";
-//TODO: Implement
-class DateRangeEditField extends EditField {
-}
-
-
-//TODO: See file wp-field.service.js:getInplaceEditStrategy for more eventual classes
-
openprojectModule
.run((wpEditField:WorkPackageEditFieldService) => {
wpEditField.defaultType = 'text';
@@ -59,10 +52,10 @@ openprojectModule
'User',
'Version',
'Category',
- 'StringObject',
+ 'CustomOption',
'Project'])
.addFieldType(MultiSelectEditField, 'multi-select', [
- '[]StringObject',
+ '[]CustomOption',
'[]User'
])
.addFieldType(FloatEditField, 'float', ['Float'])
diff --git a/lib/api/decorators/aggregation_group.rb b/lib/api/decorators/aggregation_group.rb
index e562cdc733..9f692dddc3 100644
--- a/lib/api/decorators/aggregation_group.rb
+++ b/lib/api/decorators/aggregation_group.rb
@@ -35,7 +35,7 @@ module API
@sums = sums
@query = query
- group_key = set_links!(query, group_key) || group_key
+ group_key = set_links!(group_key) || group_key
@link = ::API::V3::Utilities::ResourceLinkGenerator.make_link(group_key)
@@ -92,35 +92,20 @@ module API
:query
##
- # Initializes the links collection for this group if the query is being grouped by
- # a multi value custom field. In that case an updated group_key is returned too.
+ # Initializes the links collection for this group if the group has multiple keys
#
# @return [String] A new group key for the multi value custom field.
- def set_links!(query, group_key)
- if multi_value_custom_field? query
- options = link_options query, group_key
-
- if options
- @links = options.map do |opt|
- {
- href: ::API::V3::Utilities::ResourceLinkGenerator.make_link(opt.id.to_s),
- title: opt.value
- }
- end
-
- options.map(&:value).join(", ")
+ def set_links!(group_key)
+ if group_key.is_a?(Array)
+ @links = group_key.map do |opt|
+ {
+ href: ::API::V3::Utilities::ResourceLinkGenerator.make_link(opt),
+ title: opt.to_s
+ }
end
- end
- end
-
- def multi_value_custom_field?(query)
- column = query.group_by_column
- column.is_a?(QueryCustomFieldColumn) && column.custom_field.multi_value?
- end
-
- def link_options(query, group_key)
- query.group_by_column.custom_field.custom_options.where(id: group_key.to_s.split("."))
+ group_key.map(&:name).sort.join(", ")
+ end
end
def value
diff --git a/lib/api/v3/custom_options/custom_option_representer.rb b/lib/api/v3/custom_options/custom_option_representer.rb
new file mode 100644
index 0000000000..fa3c2d8dd2
--- /dev/null
+++ b/lib/api/v3/custom_options/custom_option_representer.rb
@@ -0,0 +1,48 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2017 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module API
+ module V3
+ module CustomOptions
+ class CustomOptionRepresenter < ::API::Decorators::Single
+ self_link
+
+ # TODO: add link to custom field once api for custom fields exists
+
+ def _type
+ 'CustomOption'
+ end
+
+ property :id
+
+ property :value
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/custom_options/custom_options_api.rb b/lib/api/v3/custom_options/custom_options_api.rb
new file mode 100644
index 0000000000..3a5ecb8f4c
--- /dev/null
+++ b/lib/api/v3/custom_options/custom_options_api.rb
@@ -0,0 +1,67 @@
+#-- encoding: UTF-8
+
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2017 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module API
+ module V3
+ module CustomOptions
+ class CustomOptionsAPI < ::API::OpenProjectAPI
+ resources :custom_options do
+ namespace ':id' do
+ params do
+ requires :id, type: Integer
+ end
+
+ helpers do
+ def authorize_view_in_activated_project(custom_option)
+ allowed = Project
+ .allowed_to(current_user, :view_work_packages)
+ .joins(:work_package_custom_fields)
+ .where(custom_fields: { id: custom_option.custom_field_id })
+ .exists?
+
+ unless allowed
+ raise API::Errors::NotFound
+ end
+ end
+ end
+
+ get do
+ co = CustomOption.find(params[:id])
+
+ authorize_view_in_activated_project(co)
+
+ CustomOptionRepresenter.new(co, current_user: current_user)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/queries/schemas/string_object_filter_dependency_representer.rb b/lib/api/v3/queries/schemas/custom_option_filter_dependency_representer.rb
similarity index 86%
rename from lib/api/v3/queries/schemas/string_object_filter_dependency_representer.rb
rename to lib/api/v3/queries/schemas/custom_option_filter_dependency_representer.rb
index eb6893693e..f0c6b26d38 100644
--- a/lib/api/v3/queries/schemas/string_object_filter_dependency_representer.rb
+++ b/lib/api/v3/queries/schemas/custom_option_filter_dependency_representer.rb
@@ -1,4 +1,5 @@
#-- encoding: UTF-8
+
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@@ -31,7 +32,7 @@ module API
module V3
module Queries
module Schemas
- class StringObjectFilterDependencyRepresenter <
+ class CustomOptionFilterDependencyRepresenter <
FilterDependencyRepresenter
schema_with_allowed_collection :values,
@@ -41,13 +42,13 @@ module API
required: true,
visibility: false,
values_callback: ->(*) {
- represented.allowed_values
+ represented.custom_field.custom_options
},
- value_representer: StringObjects::StringObjectRepresenter,
+ value_representer: CustomOptions::CustomOptionRepresenter,
link_factory: ->(value) {
{
- href: api_v3_paths.string_object(value),
- title: value
+ href: api_v3_paths.custom_option(value),
+ title: value.to_s
}
},
show_if: ->(*) {
@@ -57,7 +58,7 @@ module API
private
def type
- '[]StringObject'
+ '[]CustomOption'
end
end
end
diff --git a/lib/api/v3/queries/schemas/filter_dependency_representer_factory.rb b/lib/api/v3/queries/schemas/filter_dependency_representer_factory.rb
index 564e40c6c7..532b3d0f78 100644
--- a/lib/api/v3/queries/schemas/filter_dependency_representer_factory.rb
+++ b/lib/api/v3/queries/schemas/filter_dependency_representer_factory.rb
@@ -84,7 +84,7 @@ module API
case format
when 'list'
- 'API::V3::Queries::Schemas::StringObjectFilterDependencyRepresenter'
+ 'API::V3::Queries::Schemas::CustomOptionFilterDependencyRepresenter'
when 'bool'
'API::V3::Queries::Schemas::BooleanFilterDependencyRepresenter'
when 'user', 'version', 'float'
diff --git a/lib/api/v3/root.rb b/lib/api/v3/root.rb
index 4215942141..7531740a37 100644
--- a/lib/api/v3/root.rb
+++ b/lib/api/v3/root.rb
@@ -38,6 +38,7 @@ module API
mount ::API::V3::Attachments::AttachmentsAPI
mount ::API::V3::Categories::CategoriesAPI
mount ::API::V3::Configuration::ConfigurationAPI
+ mount ::API::V3::CustomOptions::CustomOptionsAPI
mount ::API::V3::Principals::PrincipalsAPI
mount ::API::V3::Priorities::PrioritiesAPI
mount ::API::V3::Projects::ProjectsAPI
diff --git a/lib/api/v3/utilities/custom_field_injector.rb b/lib/api/v3/utilities/custom_field_injector.rb
index 8de6487be4..04ed338047 100644
--- a/lib/api/v3/utilities/custom_field_injector.rb
+++ b/lib/api/v3/utilities/custom_field_injector.rb
@@ -40,7 +40,7 @@ module API
'bool' => 'Boolean',
'user' => 'User',
'version' => 'Version',
- 'list' => 'StringObject'
+ 'list' => 'CustomOption'
}.freeze
LINK_FORMATS = ['list', 'user', 'version'].freeze
@@ -48,19 +48,19 @@ module API
PATH_METHOD_MAP = {
'user' => :user,
'version' => :version,
- 'list' => :string_object
+ 'list' => :custom_option
}.freeze
NAMESPACE_MAP = {
'user' => 'users',
'version' => 'versions',
- 'list' => 'string_objects'
+ 'list' => 'custom_options'
}.freeze
REPRESENTER_MAP = {
'user' => Users::UserRepresenter,
'version' => Versions::VersionRepresenter,
- 'list' => StringObjects::StringObjectRepresenter
+ 'list' => CustomOptions::CustomOptionRepresenter
}.freeze
class << self
@@ -182,14 +182,14 @@ module API
@class.schema_with_allowed_collection property_name(custom_field.id),
type: 'Version',
- name_source: -> (*) { custom_field.name },
- values_callback: -> (*) {
+ name_source: ->(*) { custom_field.name },
+ values_callback: ->(*) {
customized
.assignable_custom_field_values(custom_field)
},
writable: true,
value_representer: Versions::VersionRepresenter,
- link_factory: -> (version) {
+ link_factory: ->(version) {
{
href: api_v3_paths.version(version.id),
title: version.name
@@ -216,16 +216,14 @@ module API
end
def inject_list_schema(custom_field, customized)
- representer = StringObjects::StringObjectRepresenter
- type = custom_field.multi_value ? "[]StringObject" : "StringObject"
+ representer = CustomOptions::CustomOptionRepresenter
+ type = custom_field.multi_value ? "[]CustomOption" : "CustomOption"
name_source = ->(*) { custom_field.name }
values_callback = ->(*) { customized.assignable_custom_field_values(custom_field) }
link_factory = ->(value) do
- # allow both single values and tuples for
- # custom titles
{
- href: api_v3_paths.string_object(value),
- title: Array(value).first
+ href: api_v3_paths.custom_option(value.id),
+ title: value.to_s
}
end
diff --git a/lib/api/v3/utilities/custom_field_injector/link_value_getter.rb b/lib/api/v3/utilities/custom_field_injector/link_value_getter.rb
index aab64fe2ab..1f4d86dee8 100644
--- a/lib/api/v3/utilities/custom_field_injector/link_value_getter.rb
+++ b/lib/api/v3/utilities/custom_field_injector/link_value_getter.rb
@@ -59,11 +59,10 @@ module API
Array(represented.custom_value_for(custom_field)).flat_map do |custom_value|
if custom_value && custom_value.value.present?
title = link_value_title(custom_value)
- params = link_value_params(title, custom_field, custom_value)
[{
title: title,
- href: api_v3_paths.send(path_method, params)
+ href: api_v3_paths.send(path_method, custom_value.value)
}]
else
[]
@@ -79,17 +78,8 @@ module API
end
end
- def link_value_params(title, custom_field, custom_value)
- if custom_field.list?
- # list custom_fields values use string objects which support and need titles
- [title, custom_value.value]
- else
- custom_value.value
- end
- end
-
##
- # While multi value custom fields are expected to simpl return an empty array
+ # While multi value custom fields are expected to simply return an empty array
# if they have no value a normal single value custom field is expected by
# the frontend to return a single element with a null href and title.
def single_empty_value
diff --git a/lib/api/v3/utilities/path_helper.rb b/lib/api/v3/utilities/path_helper.rb
index 4e82b524c9..bc3d627edc 100644
--- a/lib/api/v3/utilities/path_helper.rb
+++ b/lib/api/v3/utilities/path_helper.rb
@@ -107,6 +107,10 @@ module API
"#{work_packages_by_project(project_id)}/form"
end
+ def self.custom_option(id)
+ "#{root}/custom_options/#{id}"
+ end
+
def self.my_preferences
"#{root}/my_preferences"
end
@@ -262,18 +266,9 @@ module API
"#{statuses}/#{id}"
end
- ##
- # Accepts either a single value or a [value, title] tuple (array)
- # and returns an URL to a string object for it.
def self.string_object(value)
- val, title = Array(value).reverse.map { |v| ::ERB::Util::url_encode(v) }
- path = "#{root}/string_objects?value=#{val}"
-
- if title
- "#{path}&title=#{title}"
- else
- path
- end
+ val = ::ERB::Util::url_encode(value)
+ "#{root}/string_objects?value=#{val}"
end
def self.types
diff --git a/lib/api/v3/work_packages/schema/specific_work_package_schema.rb b/lib/api/v3/work_packages/schema/specific_work_package_schema.rb
index 3e04d7c41a..b8da906f85 100644
--- a/lib/api/v3/work_packages/schema/specific_work_package_schema.rb
+++ b/lib/api/v3/work_packages/schema/specific_work_package_schema.rb
@@ -66,7 +66,7 @@ module API
def assignable_custom_field_values(custom_field)
case custom_field.field_format
when 'list'
- custom_field.custom_options.map { |co| [co.value, co.id] }
+ custom_field.possible_values
when 'version'
assignable_values(:version, nil)
end
diff --git a/spec/factories/custom_option_factory.rb b/spec/factories/custom_option_factory.rb
new file mode 100644
index 0000000000..f92c72be36
--- /dev/null
+++ b/spec/factories/custom_option_factory.rb
@@ -0,0 +1,33 @@
+#-- 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.
+#++
+
+FactoryGirl.define do
+ factory :custom_option do
+ sequence(:value) { |n| "Custom Option #{n}" }
+ end
+end
diff --git a/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb b/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb
index c7981df7fc..b8ee1a14dc 100644
--- a/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb
+++ b/spec/features/work_packages/details/inplace_editor/custom_field_spec.rb
@@ -86,7 +86,6 @@ describe 'custom field inplace editor', js: true do
message: I18n.t('js.notice_successful_update'),
field: field1
-
field2.activate!
expect_update 'Y',
message: I18n.t('js.notice_successful_update'),
@@ -96,11 +95,11 @@ describe 'custom field inplace editor', js: true do
customField2: 'Y'
field1.activate!
- field1.expect_value("/api/v3/string_objects?value=#{custom_value('bar')}&title=bar")
+ field1.expect_value("/api/v3/custom_options/#{custom_value('bar')}")
field1.cancel_by_escape
field2.activate!
- field2.expect_value("/api/v3/string_objects?value=#{custom_value('Y')}&title=Y")
+ field2.expect_value("/api/v3/custom_options/#{custom_value('Y')}")
expect_update 'X',
message: I18n.t('js.notice_successful_update'),
field: field2
diff --git a/spec/lib/api/v3/custom_options/custom_option_representer_spec.rb b/spec/lib/api/v3/custom_options/custom_option_representer_spec.rb
new file mode 100644
index 0000000000..354334f70b
--- /dev/null
+++ b/spec/lib/api/v3/custom_options/custom_option_representer_spec.rb
@@ -0,0 +1,64 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2017 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::CustomOptions::CustomOptionRepresenter do
+ include ::API::V3::Utilities::PathHelper
+
+ let(:custom_option) { FactoryGirl.build_stubbed(:custom_option, custom_field: custom_field) }
+ let(:custom_field) { FactoryGirl.build_stubbed(:list_wp_custom_field) }
+ let(:user) { FactoryGirl.build_stubbed(:user) }
+ let(:representer) do
+ described_class.new(custom_option, current_user: user)
+ end
+
+ subject { representer.to_json }
+
+ describe 'generation' do
+ describe '_links' do
+ it_behaves_like 'has a titled link' do
+ let(:link) { 'self' }
+ let(:href) { api_v3_paths.custom_option custom_option.id }
+ let(:title) { custom_option.to_s }
+ end
+ end
+
+ it 'has the type "CustomOption"' do
+ is_expected.to be_json_eql('CustomOption'.to_json).at_path('_type')
+ end
+
+ it 'has an id' do
+ is_expected.to be_json_eql(custom_option.id.to_json).at_path('id')
+ end
+
+ it 'has a value' do
+ is_expected.to be_json_eql(custom_option.to_s.to_json).at_path('value')
+ end
+ end
+end
diff --git a/spec/lib/api/v3/queries/schemas/string_object_filter_dependency_representer_spec.rb b/spec/lib/api/v3/queries/schemas/custom_option_filter_dependency_representer_spec.rb
similarity index 92%
rename from spec/lib/api/v3/queries/schemas/string_object_filter_dependency_representer_spec.rb
rename to spec/lib/api/v3/queries/schemas/custom_option_filter_dependency_representer_spec.rb
index db574c10a0..7005362f6e 100644
--- a/spec/lib/api/v3/queries/schemas/string_object_filter_dependency_representer_spec.rb
+++ b/spec/lib/api/v3/queries/schemas/custom_option_filter_dependency_representer_spec.rb
@@ -28,7 +28,7 @@
require 'spec_helper'
-describe ::API::V3::Queries::Schemas::StringObjectFilterDependencyRepresenter do
+describe ::API::V3::Queries::Schemas::CustomOptionFilterDependencyRepresenter do
include ::API::V3::Utilities::PathHelper
let(:project) { FactoryGirl.build_stubbed(:project) }
@@ -52,10 +52,10 @@ describe ::API::V3::Queries::Schemas::StringObjectFilterDependencyRepresenter do
context 'properties' do
describe 'values' do
let(:path) { 'values' }
- let(:type) { '[]StringObject' }
+ let(:type) { '[]CustomOption' }
let(:hrefs) do
- filter.allowed_values.each_with_object([]) do |value, array|
- array << api_v3_paths.string_object(value)
+ custom_field.custom_options.map do |value|
+ api_v3_paths.custom_option(value)
end
end
diff --git a/spec/lib/api/v3/queries/schemas/filter_dependency_representer_factory_spec.rb b/spec/lib/api/v3/queries/schemas/filter_dependency_representer_factory_spec.rb
index 7e2cedd2b6..07814a9e93 100644
--- a/spec/lib/api/v3/queries/schemas/filter_dependency_representer_factory_spec.rb
+++ b/spec/lib/api/v3/queries/schemas/filter_dependency_representer_factory_spec.rb
@@ -105,7 +105,7 @@ describe ::API::V3::Queries::Schemas::FilterDependencyRepresenterFactory do
let(:custom_field) { FactoryGirl.build_stubbed(:list_wp_custom_field) }
it 'is the string object dependency' do
- is_expected.to be_a(::API::V3::Queries::Schemas::StringObjectFilterDependencyRepresenter)
+ is_expected.to be_a(::API::V3::Queries::Schemas::CustomOptionFilterDependencyRepresenter)
end
end
diff --git a/spec/lib/api/v3/utilities/custom_field_injector_spec.rb b/spec/lib/api/v3/utilities/custom_field_injector_spec.rb
index f5d07d687d..c9b1d43178 100644
--- a/spec/lib/api/v3/utilities/custom_field_injector_spec.rb
+++ b/spec/lib/api/v3/utilities/custom_field_injector_spec.rb
@@ -155,7 +155,7 @@ describe ::API::V3::Utilities::CustomFieldInjector do
allow(schema)
.to receive(:assignable_custom_field_values)
.with(custom_field)
- .and_return(custom_field.possible_values.map { |co| [co.value, co.id] })
+ .and_return(custom_field.possible_values)
end
let(:custom_field) do
@@ -170,7 +170,7 @@ describe ::API::V3::Utilities::CustomFieldInjector do
it_behaves_like 'has basic schema properties' do
let(:path) { cf_path }
- let(:type) { 'StringObject' }
+ let(:type) { 'CustomOption' }
let(:name) { custom_field.name }
let(:required) { true }
let(:writable) { true }
@@ -180,7 +180,7 @@ describe ::API::V3::Utilities::CustomFieldInjector do
let(:path) { cf_path }
let(:hrefs) do
custom_field.possible_values.map do |value|
- api_v3_paths.string_object([value.value, value.id])
+ api_v3_paths.custom_option(value.id)
end
end
end
@@ -297,18 +297,21 @@ describe ::API::V3::Utilities::CustomFieldInjector do
end
context 'list custom field' do
- let(:value) { 'Foobar' }
- let(:raw_value) { value }
+ let(:value) { FactoryGirl.build_stubbed(:custom_option) }
+ let(:typed_value) { value.value }
+ let(:raw_value) { value.id.to_s }
let(:field_format) { 'list' }
it_behaves_like 'has a titled link' do
let(:link) { cf_path }
- let(:href) { "/api/v3/string_objects?value=#{raw_value}&title=#{value}" }
- let(:title) { value }
+ let(:href) { api_v3_paths.custom_option(value.id) }
+ let(:title) { value.value }
end
context 'value is nil' do
let(:value) { nil }
+ let(:raw_value) { '' }
+ let(:typed_value) { '' }
it_behaves_like 'has an empty link' do
let(:link) { cf_path }
diff --git a/spec/lib/api/v3/utilities/path_helper_spec.rb b/spec/lib/api/v3/utilities/path_helper_spec.rb
index 93a4aa2e87..3d0ce3537b 100644
--- a/spec/lib/api/v3/utilities/path_helper_spec.rb
+++ b/spec/lib/api/v3/utilities/path_helper_spec.rb
@@ -140,6 +140,12 @@ describe ::API::V3::Utilities::PathHelper do
it_behaves_like 'api v3 path', '/configuration'
end
+ describe '#custom_option' do
+ subject { helper.custom_option 42 }
+
+ it_behaves_like 'api v3 path', '/custom_options/42'
+ end
+
describe '#create_work_package_form' do
subject { helper.create_work_package_form }
diff --git a/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_spec.rb b/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_spec.rb
index b17efc964e..deee5724db 100644
--- a/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_spec.rb
+++ b/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_spec.rb
@@ -263,7 +263,7 @@ describe ::API::V3::WorkPackages::Schema::SpecificWorkPackageSchema do
it "is a list custom fields' possible values" do
expect(subject.assignable_custom_field_values(list_cf))
- .to eql list_cf.possible_values.map { |co| [co.value, co.id] }
+ .to eql list_cf.possible_values
end
it "is a version custom fields' project values" do
diff --git a/spec/models/queries/work_packages/filter/custom_field_filter_spec.rb b/spec/models/queries/work_packages/filter/custom_field_filter_spec.rb
index 10e823a711..cd350c3e55 100644
--- a/spec/models/queries/work_packages/filter/custom_field_filter_spec.rb
+++ b/spec/models/queries/work_packages/filter/custom_field_filter_spec.rb
@@ -412,20 +412,9 @@ describe Queries::WorkPackages::Filter::CustomFieldFilter, type: :model do
end
it 'returns an array with custom classes' do
- expect(instance.value_objects.length)
- .to eql(2)
-
- expect(instance.value_objects[0].id)
- .to match_array([custom_field.custom_options.first.value,
- custom_field.custom_options.first.id])
- expect(instance.value_objects[0].name)
- .to eql(custom_field.custom_options.first.value)
-
- expect(instance.value_objects[1].id)
- .to match_array([custom_field.custom_options.last.value,
- custom_field.custom_options.last.id])
- expect(instance.value_objects[1].name)
- .to eql(custom_field.custom_options.last.value)
+ expect(instance.value_objects)
+ .to match_array([custom_field.custom_options.last,
+ custom_field.custom_options.first])
end
end
end
diff --git a/spec/requests/api/v3/custom_options/custom_options_resource_spec.rb b/spec/requests/api/v3/custom_options/custom_options_resource_spec.rb
new file mode 100644
index 0000000000..0a4649421a
--- /dev/null
+++ b/spec/requests/api/v3/custom_options/custom_options_resource_spec.rb
@@ -0,0 +1,119 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2017 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+require 'spec_helper'
+require 'rack/test'
+
+describe 'API v3 Custom Options resource' do
+ include Rack::Test::Methods
+ include API::V3::Utilities::PathHelper
+
+ let(:user) do
+ FactoryGirl.create(:user,
+ member_in_project: project,
+ member_through_role: role)
+ end
+ let(:project) { FactoryGirl.create(:project) }
+ let(:role) { FactoryGirl.create(:role, permissions: permissions) }
+ let(:permissions) { [:view_work_packages] }
+ let(:custom_field) do
+ cf = FactoryGirl.create(:list_wp_custom_field)
+
+ project.work_package_custom_fields << cf
+
+ cf
+ end
+ let(:custom_option) do
+ FactoryGirl.create(:custom_option,
+ custom_field: custom_field)
+ end
+
+ subject(:response) { last_response }
+
+ describe 'GET api/v3/custom_options/:id' do
+ let(:path) { api_v3_paths.custom_option custom_option.id }
+
+ before do
+ allow(User)
+ .to receive(:current)
+ .and_return(user)
+ get path
+ end
+
+ context 'when being allowed' do
+ it 'is successful' do
+ expect(subject.status)
+ .to eql(200)
+ end
+
+ it 'returns the custom option' do
+ expect(response.body)
+ .to be_json_eql('CustomOption'.to_json)
+ .at_path('_type')
+
+ expect(response.body)
+ .to be_json_eql(custom_option.id.to_json)
+ .at_path('id')
+
+ expect(response.body)
+ .to be_json_eql(custom_option.value.to_json)
+ .at_path('value')
+ end
+ end
+
+ context 'when lacking permission' do
+ let(:permissions) { [] }
+
+ it 'is 404' do
+ expect(subject.status)
+ .to eql(404)
+ end
+ end
+
+ context 'when custom option not in project' do
+ let(:custom_field) do
+ # not added to project
+ FactoryGirl.create(:list_wp_custom_field)
+ end
+
+ it 'is 404' do
+ expect(subject.status)
+ .to eql(404)
+ end
+ end
+
+ context 'when not existing' do
+ let(:path) { api_v3_paths.custom_option 0 }
+
+ it 'is 404' do
+ expect(subject.status)
+ .to eql(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/work_package_resource_spec.rb b/spec/requests/api/v3/work_package_resource_spec.rb
index be2974b977..5e62011b5e 100644
--- a/spec/requests/api/v3/work_package_resource_spec.rb
+++ b/spec/requests/api/v3/work_package_resource_spec.rb
@@ -833,21 +833,19 @@ describe 'API v3 Work package resource', type: :request do
end
context 'list custom field' do
- let(:custom_field) {
- FactoryGirl.create(:work_package_custom_field,
- field_format: 'list',
- is_required: false,
- possible_values: [target_value])
- }
- let(:target_value) { 'Low No. of specialc#aracters!' }
+ let(:custom_field) do
+ FactoryGirl.create(:list_wp_custom_field)
+ end
+
+ let(:target_value) { custom_field.possible_values.last }
let(:value_link) do
- api_v3_paths.string_object [target_value, custom_field.custom_options.first.id]
+ api_v3_paths.custom_option target_value.id
end
- let(:value_parameter) {
+ let(:value_parameter) do
{ _links: { custom_field.accessor_name.camelize(:lower) => { href: value_link } } }
- }
+ end
let(:params) { valid_params.merge(value_parameter) }
before do
diff --git a/spec_legacy/unit/query_spec.rb b/spec_legacy/unit/query_spec.rb
index db9447942a..9b63a0d832 100644
--- a/spec_legacy/unit/query_spec.rb
+++ b/spec_legacy/unit/query_spec.rb
@@ -366,9 +366,12 @@ describe Query, type: :model do
q = Query.new(name: '_', group_by: 'cf_1')
count_by_group = q.results.work_package_count_by_group
assert_kind_of Hash, count_by_group
- assert_equal %w(NilClass String), count_by_group.keys.map { |k| k.class.name }.uniq.sort
+ expect(count_by_group.keys.map { |k| k.class.name }.uniq)
+ .to match_array(%w(CustomOption NilClass))
assert_equal %w(Fixnum), count_by_group.values.map { |k| k.class.name }.uniq
- assert count_by_group.has_key?('1')
+ puts count_by_group
+ expect(count_by_group.any? { |k, v| k.is_a?(CustomOption) && k.id == 1 && v == 1 })
+ .to be_truthy
end
it 'should issue count by date custom field group' do