diff --git a/docs/api/apiv3/components/examples/hierarchy_item_collection_filtered_response.yml b/docs/api/apiv3/components/examples/hierarchy_item_collection_filtered_response.yml new file mode 100644 index 000000000000..8c1abe35d368 --- /dev/null +++ b/docs/api/apiv3/components/examples/hierarchy_item_collection_filtered_response.yml @@ -0,0 +1,53 @@ +description: |- + Filtered response of a hierarchy structure starting at a specific parent and restricting the depth. +value: + _type: Collection + total: 2 + count: 2 + _embedded: + elements: + - _type: HierarchyItem + id: 1337 + label: null + short: null + _links: + self: + href: /api/v3/custom_field/42/items/1337 + parent: + href: null + children: + - href: /api/v3/custom_field/42/items/1338 + name: Stormtroopers + - href: /api/v3/custom_field/42/items/1339 + name: Dark Troopers + - _type: HierarchyItem + id: 1338 + label: Stormtroopers + short: ST + _links: + self: + href: /api/v3/custom_field/42/items/1338 + parent: + href: /api/v3/custom_field/42/items/1337 + children: + - href: /api/v3/custom_field/42/items/1340 + name: ST-377 + - href: /api/v3/custom_field/42/items/1341 + name: ST-422 + - _type: HierarchyItem + id: 1339 + label: Dark Troopers + short: DT + _links: + self: + href: /api/v3/custom_field/42/items/1339 + parent: + href: /api/v3/custom_field/42/items/1337 + children: + - href: /api/v3/custom_field/42/items/1342 + name: DT-3817992 + - href: /api/v3/custom_field/42/items/1343 + name: DT-0129755 + _links: + self: + href: /api/v3/custom_field/42/items?parent=1337&depth=1 diff --git a/docs/api/apiv3/components/examples/hierarchy_item_collection_response.yml b/docs/api/apiv3/components/examples/hierarchy_item_collection_response.yml new file mode 100644 index 000000000000..ac6a9683d3c4 --- /dev/null +++ b/docs/api/apiv3/components/examples/hierarchy_item_collection_response.yml @@ -0,0 +1,45 @@ +description: |- + Simple response of a hierarchy structure starting at root. +value: + _type: Collection + total: 37 + count: 37 + _embedded: + elements: + - _type: HierarchyItem + id: 1337 + label: null + short: null + _links: + self: + href: /api/v3/custom_field/42/items/1337 + parent: + href: null + children: + - href: /api/v3/custom_field/42/items/1338 + name: Stormtroopers + - href: /api/v3/custom_field/42/items/1339 + name: Dark Troopers + - _type: HierarchyItem + id: 1338 + label: Stormtroopers + short: ST + _links: + self: + href: /api/v3/custom_field/42/items/1338 + parent: + href: /api/v3/custom_field/42/items/1337 + children: + - href: /api/v3/custom_field/42/items/1340 + name: ST-377 + - href: /api/v3/custom_field/42/items/1341 + name: ST-422 + - _type: HierarchyItem + _hint: hierarchy item shortened for brevity + id: 1340 + label: ST-377 + short: null + - _hint: hierarchy item shortened for brevity + _links: + self: + href: /api/v3/custom_field/42/items diff --git a/docs/api/apiv3/components/schemas/hierarchy_item_collection_model.yml b/docs/api/apiv3/components/schemas/hierarchy_item_collection_model.yml new file mode 100644 index 000000000000..c6b2a5894737 --- /dev/null +++ b/docs/api/apiv3/components/schemas/hierarchy_item_collection_model.yml @@ -0,0 +1,30 @@ +# Schema: HierarchyItemCollectionModel +--- +allOf: + - $ref: './collection_model.yml' + - type: object + required: + - _links + - _embedded + properties: + _links: + type: object + required: + - self + properties: + self: + allOf: + - $ref: './link.yml' + - description: |- + This hierarchy item collection + + **Resource**: HierarchyItemCollectionModel + _embedded: + type: object + required: + - elements + properties: + elements: + type: array + items: + $ref: './hierarchy_item_read_model.yml' diff --git a/docs/api/apiv3/components/schemas/hierarchy_item_read_model.yml b/docs/api/apiv3/components/schemas/hierarchy_item_read_model.yml new file mode 100644 index 000000000000..302c21756e8c --- /dev/null +++ b/docs/api/apiv3/components/schemas/hierarchy_item_read_model.yml @@ -0,0 +1,43 @@ +# Schema: HierarchyItemReadModel +--- +type: object +properties: + _type: + type: string + enum: + - HierarchyItem + id: + type: integer + description: Hierarchy item identifier + label: + type: string + description: The label of the hierarchy item + short: + type: string + description: The short name of the hierarchy item + _links: + type: object + properties: + self: + allOf: + - $ref: './link.yml' + - description: |- + This hierarchy item + + **Resource**: HierarchyItem + parent: + allOf: + - $ref: './link.yml' + - description: |- + The hierarchy item that is the parent of the current hierarchy item + + **Resource**: HierarchyItem + children: + type: array + items: + allOf: + - $ref: './link.yml' + - description: |- + A hierarchy item that is a child of the current hierarchy item + + **Resource**: HierarchyItem diff --git a/docs/api/apiv3/openapi-spec.yml b/docs/api/apiv3/openapi-spec.yml index 9175c3f08b8b..1865613e7854 100644 --- a/docs/api/apiv3/openapi-spec.yml +++ b/docs/api/apiv3/openapi-spec.yml @@ -186,6 +186,8 @@ paths: "$ref": "./paths/custom_action.yml" "/api/v3/custom_actions/{id}/execute": "$ref": "./paths/custom_action_execute.yml" + "/api/v3/custom_fields/{id}/items": + "$ref": "./paths/custom_field_items.yml" "/api/v3/custom_options/{id}": "$ref": "./paths/custom_option.yml" "/api/v3/days/non_working": @@ -489,6 +491,10 @@ components: $ref: "./components/examples/grid-simple-response.yml" GroupResponse: $ref: "./components/examples/group-response.yml" + HierarchyItemCollectionFilteredResponse: + $ref: "./components/examples/hierarchy_item_collection_filtered_response.yml" + HierarchyItemCollectionResponse: + $ref: "./components/examples/hierarchy_item_collection_response.yml" MembershipCreateRequestCustomMessage: $ref: "./components/examples/membership-create-request-custom-message.yml" MembershipCreateRequestGlobalRole: @@ -673,6 +679,10 @@ components: "$ref": "./components/schemas/help_text_collection_model.yml" HelpTextModel: "$ref": "./components/schemas/help_text_model.yml" + HierarchyItemCollectionModel: + "$ref": "./components/schemas/hierarchy_item_collection_model.yml" + HierarchyItemReadModel: + "$ref": "./components/schemas/hierarchy_item_read_model.yml" Link: "$ref": "./components/schemas/link.yml" List_actionsModel: @@ -946,4 +956,4 @@ tags: - "$ref": "./tags/work_packages.yml" - "$ref": "./tags/work_schedule.yml" security: - - BasicAuth: [] + - BasicAuth: [ ] diff --git a/docs/api/apiv3/paths/custom_field_items.yml b/docs/api/apiv3/paths/custom_field_items.yml new file mode 100644 index 000000000000..4e47755bbc3e --- /dev/null +++ b/docs/api/apiv3/paths/custom_field_items.yml @@ -0,0 +1,71 @@ +# /api/v3/custom_field/{id}/items +--- +get: + summary: Get the custom field hierarchy items + operationId: get_custom_field_items + description: |- + Retrieves the hierarchy of custom fields. + + The hierarchy is a tree structure of hierarchy items. It is represented as a flat list of items, where each item + has a reference to its parent and children. The list is ordered in a depth-first manner. The first item is the + requested parent. If parent was unset, the root item is returned as first element. + + This endpoint only returns, if the custom field is of type `hierarchy`. + parameters: + - name: id + description: The custom field's unique identifier + in: path + example: '42' + required: true + schema: + type: integer + - name: parent + description: The identifier of the parent hierarchy item + in: query + example: '1337' + required: false + schema: + type: integer + - name: depth + description: The level of hierarchy depth + in: query + example: '1' + required: false + schema: + type: integer + responses: + '200': + description: OK + content: + application/hal+json: + schema: + $ref: '../components/schemas/hierarchy_item_collection_model.yml' + examples: + 'simple response': + $ref: '../components/examples/hierarchy_item_collection_response.yml' + 'filtered response': + $ref: '../components/examples/hierarchy_item_collection_filtered_response.yml' + '404': + description: Returned if the custom field does not exist. + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + examples: + response: + value: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:NotFound + message: The requested resource could not be found. + '422': + description: Returned if the custom field is not of type hierarchy. + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + examples: + response: + value: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:UnprocessableContent + message: The requested custom field resource is of wrong type. diff --git a/lib/api/errors/unprocessable_content.rb b/lib/api/errors/unprocessable_content.rb new file mode 100644 index 000000000000..95537b8bd732 --- /dev/null +++ b/lib/api/errors/unprocessable_content.rb @@ -0,0 +1,43 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module API + module Errors + class UnprocessableContent < ErrorBase + identifier "UnprocessableContent" + code 422 + + def initialize(property, message) + super(message) + + @property = property + @details = { attribute: property } + end + end + end +end