Skip to content
代码片段 群组 项目
未验证 提交 d2f7b149 编辑于 作者: Nicolas Dular's avatar Nicolas Dular 提交者: GitLab
浏览文件

Merge branch 'nd/poc-related-epic-links-from-work-items' into 'master'

Fetch related epic links from work items

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179742



Merged-by: default avatarNicolas Dular <ndular@gitlab.com>
Approved-by: default avatarAgnes Slota <aslota@gitlab.com>
Approved-by: default avatarEugenia Grieff <egrieff@gitlab.com>
Reviewed-by: default avatarNicolas Dular <ndular@gitlab.com>
Reviewed-by: default avatarAgnes Slota <aslota@gitlab.com>
No related branches found
No related tags found
2 合并请求!3031Merge per-main-jh to main-jh by luzhiyuan,!3030Merge per-main-jh to main-jh
显示
541 个添加303 个删除
......@@ -3,6 +3,8 @@
module WorkItems
class RelatedWorkItemLink < ApplicationRecord
include LinkableItem
include CreatedAtFilterable
include UpdatedAtFilterable
self.table_name = 'issue_links'
......
......@@ -10,6 +10,11 @@ module RelatedWorkItemLink
prepended do
has_one :related_epic_link, class_name: '::Epic::RelatedEpicLink', foreign_key: 'issue_link_id',
inverse_of: :related_work_item_link
scope :for_source_type, ->(type) { joins(source: [:work_item_type]).where(source: { work_item_type_id: type }) }
scope :for_target_type, ->(type) { joins(target: [:work_item_type]).where(target: { work_item_type_id: type }) }
scope :preload_for_epic_link, -> { preload(:related_epic_link, source: [:synced_epic], target: [:synced_epic]) }
end
override :validate_related_link_restrictions
......
# frozen_string_literal: true
module WorkItems
module LegacyEpics
module RelatedEpicLinks
class ListService
include Gitlab::Utils::StrongMemoize
def initialize(legacy_epics, group)
@legacy_epics = legacy_epics
@group = group
end
def execute
if Feature.enabled?(:related_epic_links_from_work_items, group)
WorkItems::RelatedWorkItemLink
.for_source_or_target(legacy_epics.select(:issue_id))
.for_source_type(epic_type)
.for_target_type(epic_type)
.preload_for_epic_link
else
Epic::RelatedEpicLink.for_source_or_target(legacy_epics)
end
end
private
def epic_type
::WorkItems::Type.default_by_type(:epic)
end
strong_memoize_attr :epic_type
attr_reader :legacy_epics, :group
end
end
end
end
---
name: related_epic_links_from_work_items
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/502553
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179742
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/516212
milestone: '17.10'
group: group::product planning
type: beta
default_enabled: false
......@@ -9,6 +9,33 @@ class RelatedEpicLink < Grape::Entity
expose :link_type, documentation: { type: "string", example: "relates_to" }
expose :created_at, documentation: { type: "dateTime", example: "2022-01-31T15:10:45.080Z" }
expose :updated_at, documentation: { type: "dateTime", example: "2022-01-31T15:10:45.080Z" }
def id
case object
when ::Epic::RelatedEpicLink
object.id
when ::WorkItems::RelatedWorkItemLink
object.related_epic_link.id
end
end
def source
case object.source
when ::Epic
object.source
when ::WorkItem
object.source.synced_epic
end
end
def target
case object.target
when ::Epic
object.target
when ::WorkItem
object.target.synced_epic
end
end
end
end
end
......@@ -65,26 +65,32 @@ def find_permissioned_epic!(iid, group_id: nil, permission: :admin_epic_link_rel
end
get ':id/related_epic_links' do
accessible_epics = EpicsFinder.new(current_user, group_id: user_group.id).execute
related_epic_links = Epic::RelatedEpicLink.for_source_or_target(accessible_epics)
related_epic_links = related_epic_links.updated_before(params[:updated_before]) if params[:updated_before]
related_epic_links = related_epic_links.updated_after(params[:updated_after]) if params[:updated_after]
related_epic_links = related_epic_links.created_before(params[:created_before]) if params[:created_before]
related_epic_links = related_epic_links.created_after(params[:created_after]) if params[:created_after]
related_links = ::WorkItems::LegacyEpics::RelatedEpicLinks::ListService
.new(accessible_epics, user_group).execute
related_epic_links = paginate(related_epic_links).with_api_entity_associations
related_epic_links.each { |link| [link.source, link.target].each(&:lazy_labels) }
related_links = related_links.updated_before(params[:updated_before]) if params[:updated_before]
related_links = related_links.updated_after(params[:updated_after]) if params[:updated_after]
related_links = related_links.created_before(params[:created_before]) if params[:created_before]
related_links = related_links.created_after(params[:created_after]) if params[:created_after]
related_links = paginate(related_links)
related_links.each { |link| [link.source, link.target].each(&:lazy_labels) }
# EpicLinks can link to other Epics the user has no access to.
# For these epics we need to check permissions.
related_epic_links = related_epic_links.select do |related_epic_link|
related_epic_link.source.readable_by?(current_user) && related_epic_link.target.readable_by?(current_user)
related_links = related_links.select do |related_link|
related_link.source.readable_by?(current_user) && related_link.target.readable_by?(current_user)
end
source_and_target_epics = related_epic_links.reduce(Set.new) { |acc, link| acc << link.source << link.target }
source_and_target_epics = related_links.reduce(Set.new) { |acc, link| acc << link.source << link.target }
if Feature.enabled?(:related_epic_links_from_work_items, user_group)
source_and_target_epics = source_and_target_epics.map(&:synced_epic)
end
epics_metadata = Gitlab::IssuableMetadata.new(current_user, source_and_target_epics).data
present related_epic_links, issuable_metadata: epics_metadata, with: Entities::RelatedEpicLink
present related_links, issuable_metadata: epics_metadata, with: Entities::RelatedEpicLink
end
desc 'Get related epics' do
......
......@@ -7,7 +7,13 @@
trait :with_related_work_item_link do
related_work_item_link do
association(:work_item_link, source: source&.work_item, target: target&.work_item, link_type: link_type)
association(:work_item_link,
source: source&.work_item,
target: target&.work_item,
link_type: link_type,
created_at: created_at,
updated_at: updated_at
)
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::API::Entities::RelatedEpicLink, feature_category: :team_planning do
subject(:entity) { described_class.new(object).as_json }
let_it_be(:source_epic) { create(:epic) }
let_it_be(:target_epic) { create(:epic) }
let_it_be(:related_work_item_link) do
create(:work_item_link, id: 999, source: source_epic.work_item, target: target_epic.work_item)
end
let_it_be(:related_epic_link) do
# We want to ensure the `related_epic_link.id` gets used, so we set the id to a static value
create(:related_epic_link, id: 100, source: source_epic, target: target_epic,
related_work_item_link: related_work_item_link)
end
shared_examples 'exposes data correctly' do
it 'uses the data from the related epic link', :aggregate_failures do
expect(entity.keys).to contain_exactly(:id, :source_epic, :target_epic, :link_type, :created_at, :updated_at)
expect(entity[:id]).to eq(100)
expect(entity[:source_epic][:id]).to eq(source_epic.id)
expect(entity[:target_epic][:id]).to eq(target_epic.id)
expect(entity[:link_type]).to eq('relates_to')
expect(entity[:created_at]).to eq(object.created_at)
expect(entity[:updated_at]).to eq(object.updated_at)
end
end
context 'when related epic link' do
let(:object) { related_epic_link }
it_behaves_like 'exposes data correctly'
end
context 'when related work_item link' do
let(:object) { related_work_item_link }
it_behaves_like 'exposes data correctly'
end
end
......@@ -16,6 +16,40 @@
end
end
describe 'scopes' do
let(:epic_type) { ::WorkItems::Type.default_by_type(:epic) }
let_it_be(:epic_issue_link) do
create(:work_item_link, source: create(:work_item, :epic), target: create(:work_item, :issue))
end
let_it_be(:epic_epic_link) do
create(:work_item_link, source: create(:work_item, :epic), target: create(:work_item, :epic))
end
let_it_be(:issue_epic_link) do
create(:work_item_link, source: create(:work_item, :issue), target: create(:work_item, :epic))
end
context 'when filtered by source type' do
it 'returns only links with the given type on the source' do
expect(described_class.for_source_type(epic_type)).to contain_exactly(epic_issue_link, epic_epic_link)
end
end
context 'when filtered by target type' do
it 'returns only links with the given type on the target' do
expect(described_class.for_target_type(epic_type)).to contain_exactly(issue_epic_link, epic_epic_link)
end
end
context 'when combining for_target_type and for_source_type' do
it 'returns only links with the given type on the source and target' do
expect(described_class.for_source_type(epic_type).for_target_type(epic_type)).to contain_exactly(epic_epic_link)
end
end
end
describe 'validations' do
describe '#validate_related_link_restrictions' do
using RSpec::Parameterized::TableSyntax
......
此差异已折叠。
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::LegacyEpics::RelatedEpicLinks::ListService, feature_category: :team_planning do
let(:epics) { Epic.where(id: [epic1.id, epic2.id, epic3.id]) }
let_it_be(:group) { create(:group) }
let_it_be(:epic1) { create(:epic, group: group) }
let_it_be(:epic2) { create(:epic, group: group) }
let_it_be(:epic3) { create(:epic, group: group) }
let_it_be(:epic4) { create(:epic, group: group) }
let_it_be(:epic5) { create(:epic, group: group) }
let_it_be(:related_epic_link1) { create(:related_epic_link, source: epic1, target: epic2) }
let_it_be(:related_epic_link2) { create(:related_epic_link, source: epic3, target: epic4) }
let_it_be(:other_related_epic) { create(:related_epic_link, source: epic4, target: epic5) }
let_it_be(:other_work_item_link) do
create(:work_item_link, source: epic1.work_item, target: create(:work_item, :issue, namespace: group))
end
subject(:execute) { described_class.new(epics, group).execute }
describe '#execute' do
context 'when related_epic_links_from_work_items feature flag is enabled' do
before do
stub_feature_flags(related_epic_links_from_work_items: group)
end
it 'returns related work item links for epics' do
expect(execute).to contain_exactly(related_epic_link1.related_work_item_link,
related_epic_link2.related_work_item_link)
expect(execute.first.class).to eq(::WorkItems::RelatedWorkItemLink)
end
end
context 'when related_epic_links_from_work_items feature flag is disabled' do
before do
stub_feature_flags(related_epic_links_from_work_items: false)
end
it 'returns related epic links' do
expect(execute).to contain_exactly(related_epic_link1, related_epic_link2)
expect(execute.first.class).to eq(Epic::RelatedEpicLink)
end
end
end
end
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册