Commit ad51aba1 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'issue_353472-add_related_epics_list_endpoint' into 'master'

Allow to list related epics on REST API

See merge request gitlab-org/gitlab!82332
parents d9bdf62b 92d9f432
......@@ -86,6 +86,7 @@ module EE
scope :from_id, -> (epic_id) { where('epics.id >= ?', epic_id) }
scope :with_web_entity_associations, -> { preload(:author, group: [:ip_restrictions, :route]) }
scope :with_api_entity_associations, -> { preload(:author, :labels) }
scope :within_timeframe, -> (start_date, end_date) do
where('start_date is not NULL or end_date is not NULL')
......
# frozen_string_literal: true
module API
module Entities
class RelatedEpic < EE::API::Entities::Epic
expose :related_epic_link_id
expose :epic_link_type, as: :link_type
expose :related_epic_link_created_at, as: :link_created_at
expose :related_epic_link_updated_at, as: :link_updated_at
end
end
end
......@@ -11,6 +11,10 @@ module API
forbidden! unless user_group.licensed_feature_available?(:subepics)
end
def authorize_related_epics_feature!
forbidden! unless user_group.licensed_feature_available?(:related_epics)
end
def authorize_can_read!
authorize!(:read_epic, epic)
end
......@@ -56,11 +60,12 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
def epic_options
def epic_options(entity: EE::API::Entities::Epic, issuable_metadata: nil)
{
with: EE::API::Entities::Epic,
with: entity,
user: current_user,
group: user_group
group: user_group,
issuable_metadata: issuable_metadata
}
end
end
......
# frozen_string_literal: true
# These API endpoints are used to manage the relationship between one epic with other epic
# (similar to issue links). Note that this relation is different from existing
# API::EpicLinks (which is used for parent-child epic hierarchy).
module API
class RelatedEpicLinks < ::API::Base
include PaginationParams
feature_category :portfolio_management
helpers do
def authorize_related_epics_feature_flag!
not_found! unless Feature.enabled?(:related_epics_widget, user_group, default_enabled: :yaml)
end
end
helpers ::API::Helpers::EpicsHelpers
before do
authenticate_non_get!
authorize_related_epics_feature_flag!
authorize_related_epics_feature!
end
params do
requires :id, type: String, desc: 'The ID of a group'
requires :epic_iid, type: Integer, desc: 'The internal ID of a group epic'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get related epics' do
success Entities::RelatedEpic
end
get ':id/epics/:epic_iid/related_epics' do
authorize_can_read!
preload_for_collection = [group: [:saml_provider, :route]]
related_epics = epic.related_epics(current_user, preload: preload_for_collection) do |epics|
epics.with_api_entity_associations
end
epics_metadata = Gitlab::IssuableMetadata.new(current_user, related_epics).data
presenter_options = epic_options(entity: Entities::RelatedEpic, issuable_metadata: epics_metadata)
present related_epics, presenter_options
end
end
end
end
......@@ -19,6 +19,7 @@ module EE
mount ::API::EpicIssues
mount ::API::EpicLinks
mount ::API::Epics
mount ::API::RelatedEpicLinks
mount ::API::ElasticsearchIndexedNamespaces
mount ::API::Experiments
mount ::API::GeoReplication
......
{
"type": "object",
"properties" : {
"source_epic": {
"allOf": [
{ "$ref": "../../../../../../spec/fixtures/api/schemas/public_api/v4/epic.json" }
]
},
"target_epic": {
"allOf": [
{ "$ref": "../../../../../../spec/fixtures/api/schemas/public_api/v4/epic.json" }
]
},
"link_type": {
"type": "string",
"enum": ["relates_to", "blocks", "is_blocked_by"]
},
"link_created_at": { "type": "string" },
"link_updated_at": { "type": "string" }
},
"required" : [ "source_epic", "target_epic", "link_type" ]
}
{
"type": "array",
"items": {
"type": "object",
"properties" : {
"$ref": "./related_epic_link.json"
}
}
}
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::RelatedEpicLinks do
include ExternalAuthorizationServiceHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
let_it_be(:epic) { create(:epic, group: group) }
before do
stub_licensed_features(epics: true, related_epics: true)
end
shared_examples 'a not available endpoint' do
subject { perform_request(user) }
context 'when epics feature is not available' do
before do
stub_licensed_features(epics: false, related_epics: true)
end
it { is_expected.to eq(403) }
end
context 'when related_epics feature is not available ' do
before do
stub_licensed_features(epics: true, related_epics: false)
end
it { is_expected.to eq(403) }
end
context 'when related epics widget feature flag is disabled' do
before do
stub_licensed_features(epics: true, related_epics: true)
stub_feature_flags(related_epics_widget: false)
end
it { is_expected.to eq(404) }
end
end
describe 'GET /related_epics' do
def perform_request(user = nil, params = {})
get api("/groups/#{group.id}/epics/#{epic.iid}/related_epics", user), params: params
end
context 'when user cannot read epics' do
it 'returns 404' do
perform_request
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user can read epics' do
let_it_be(:group_2) { create(:group) }
let_it_be(:related_epic_link_1) { create(:related_epic_link, source: epic, target: create(:epic, group: group)) }
let_it_be(:related_epic_link_2) { create(:related_epic_link, source: epic, target: create(:epic, group: group_2)) }
before do
group.add_guest(user)
end
it_behaves_like 'a not available endpoint'
it 'returns related epics' do
perform_request(user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(response).to match_response_schema('public_api/v4/related_epic_links', dir: 'ee')
end
it 'returns multiple links without N + 1' do
perform_request(user)
control_count = ActiveRecord::QueryRecorder.new { perform_request(user) }.count
create(:related_epic_link, source: epic, target: create(:epic, group: group))
expect { perform_request(user) }.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment