Commit 2b06001c authored by Luke Duncalfe's avatar Luke Duncalfe

Return Todos for Designs via the REST API

Enables the Todo REST API to return Todos for Designs. This feature had
previously been intentionally disabled with
https://gitlab.com/gitlab-org/gitlab/merge_requests/15129

https://gitlab.com/gitlab-org/gitlab/issues/13543
parent 1afc225a
...@@ -47,6 +47,9 @@ module DesignManagement ...@@ -47,6 +47,9 @@ module DesignManagement
joins(join.join_sources).where(actions[:event].not_eq(deletion)) joins(join.join_sources).where(actions[:event].not_eq(deletion))
end end
# Scope called by our REST API to avoid N+1 problems
scope :with_api_entity_associations, -> { preload(:issue) }
# A design is current if the most recent event is not a deletion # A design is current if the most recent event is not a deletion
scope :current, -> { visible_at_version(nil) } scope :current, -> { visible_at_version(nil) }
......
---
title: Return Todos for Designs via the REST API
merge_request: 16885
author:
type: added
...@@ -185,8 +185,10 @@ module EE ...@@ -185,8 +185,10 @@ module EE
end end
module Todo module Todo
extend ::Gitlab::Utils::Override
extend ActiveSupport::Concern extend ActiveSupport::Concern
override :todo_target_class
def todo_target_class(target_type) def todo_target_class(target_type)
super super
rescue NameError rescue NameError
...@@ -194,11 +196,35 @@ module EE ...@@ -194,11 +196,35 @@ module EE
# see also https://gitlab.com/gitlab-org/gitlab-foss/issues/59719 # see also https://gitlab.com/gitlab-org/gitlab-foss/issues/59719
::EE::API::Entities.const_get(target_type, false) ::EE::API::Entities.const_get(target_type, false)
end end
override :todo_target_url
def todo_target_url(todo)
return super unless todo.target_type == ::DesignManagement::Design.name
design = todo.target
path_options = {
anchor: todo_target_anchor(todo),
vueroute: design.filename
}
::Gitlab::Routing.url_helpers.designs_project_issue_url(design.project, design.issue, path_options)
end
end end
######################## ########################
# EE-specific entities # # EE-specific entities #
######################## ########################
module DesignManagement
class Design < Grape::Entity
expose :id
expose :project_id
expose :filename
expose :image_url do |design|
::Gitlab::UrlBuilder.build(design)
end
end
end
class ProjectPushRule < Grape::Entity class ProjectPushRule < Grape::Entity
extend EntityHelpers extend EntityHelpers
expose :id, :project_id, :created_at expose :id, :project_id, :created_at
......
...@@ -21,10 +21,12 @@ module EE ...@@ -21,10 +21,12 @@ module EE
override :find_todos override :find_todos
def find_todos def find_todos
# Todos for Designs are intentionally not supported yet. todos = super
# https://gitlab.com/gitlab-org/gitlab/issues/13543
# https://gitlab.com/gitlab-org/gitlab/issues/13494#note_203518559 return todos if ::Feature.enabled?(:design_management_todos_api, default_enabled: true)
super.where.not(target_type: ::DesignManagement::Design.name) # rubocop: disable CodeReuse/ActiveRecord
# Exclude Design Todos if the feature is disabled
todos.where.not(target_type: ::DesignManagement::Design.name) # rubocop: disable CodeReuse/ActiveRecord
end end
end end
......
# frozen_string_literal: true
module EE
module Gitlab
module UrlBuilder
extend ::Gitlab::Utils::Override
override :url
def url
return project_design_url(object.project, object) if object.is_a?(DesignManagement::Design)
super
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe EE::API::Entities::DesignManagement::Design do
set(:design) { create(:design) }
let(:entity) { described_class.new(design, request: double) }
subject { entity.as_json }
it 'has the correct attributes' do
expect(subject).to eq({
id: design.id,
project_id: design.project_id,
filename: design.filename,
image_url: ::Gitlab::UrlBuilder.build(design)
})
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::UrlBuilder do
describe '.build' do
context 'when passing a DesignManagement::Design' do
it 'returns a proper URL' do
design = build_stubbed(:design)
url = described_class.build(design)
expect(url).to eq "#{Settings.gitlab['url']}/#{design.project.full_path}/-/designs/#{design.id}"
end
end
end
end
...@@ -34,6 +34,12 @@ describe API::Todos do ...@@ -34,6 +34,12 @@ describe API::Todos do
note: create(:note, project: project, note: "I am note, hear me roar")) note: create(:note, project: project, note: "I am note, hear me roar"))
end end
shared_examples 'an endpoint that responds with success' do
it do
expect(response.status).to eq(200)
end
end
context 'when there is an Epic Todo' do context 'when there is an Epic Todo' do
let!(:epic_todo) { create_todo_for_new_epic } let!(:epic_todo) { create_todo_for_new_epic }
...@@ -41,9 +47,7 @@ describe API::Todos do ...@@ -41,9 +47,7 @@ describe API::Todos do
get api('/todos', personal_access_token: pat) get api('/todos', personal_access_token: pat)
end end
it 'responds without error' do it_behaves_like 'an endpoint that responds with success'
expect(response.status).to eq(200)
end
it 'avoids N+1 queries', :request_store do it 'avoids N+1 queries', :request_store do
create_todo_for_new_epic create_todo_for_new_epic
...@@ -65,16 +69,43 @@ describe API::Todos do ...@@ -65,16 +69,43 @@ describe API::Todos do
context 'when there is a Design Todo' do context 'when there is a Design Todo' do
let!(:design_todo) { create_todo_for_mentioned_in_design } let!(:design_todo) { create_todo_for_mentioned_in_design }
before do def api_request
get api('/todos', personal_access_token: pat) get api('/todos', personal_access_token: pat)
end end
it 'responds without error' do context 'when the feature is enabled' do
expect(response.status).to eq(200) before do
api_request
end
it_behaves_like 'an endpoint that responds with success'
it 'avoids N+1 queries', :request_store do
control = ActiveRecord::QueryRecorder.new { api_request }
create_todo_for_mentioned_in_design
expect { api_request }.not_to exceed_query_limit(control)
end
it 'includes the Design Todo in the response' do
expect(json_response).to include(
a_hash_including('id' => design_todo.id)
)
end
end end
it 'does not include the Design Todo in the response' do context 'when the feature is disabled' do
expect(json_response).to be_empty before do
stub_feature_flags(design_management_todos_api: false)
api_request
end
it_behaves_like 'an endpoint that responds with success'
it 'does not include the Design Todo in the response' do
expect(json_response).to be_empty
end
end end
end end
end end
......
...@@ -965,13 +965,7 @@ module API ...@@ -965,13 +965,7 @@ module API
end end
expose :target_url do |todo, options| expose :target_url do |todo, options|
target_type = todo.target_type.underscore todo_target_url(todo)
target_url = "#{todo.parent.class.to_s.underscore}_#{target_type}_url"
target_anchor = "note_#{todo.note_id}" if todo.note_id?
Gitlab::Routing
.url_helpers
.public_send(target_url, todo.parent, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend
end end
expose :body expose :body
...@@ -983,6 +977,19 @@ module API ...@@ -983,6 +977,19 @@ module API
# see also https://gitlab.com/gitlab-org/gitlab-foss/issues/59719 # see also https://gitlab.com/gitlab-org/gitlab-foss/issues/59719
::API::Entities.const_get(target_type, false) ::API::Entities.const_get(target_type, false)
end end
def todo_target_url(todo)
target_type = todo.target_type.underscore
target_url = "#{todo.parent.class.to_s.underscore}_#{target_type}_url"
Gitlab::Routing
.url_helpers
.public_send(target_url, todo.parent, todo.target, anchor: todo_target_anchor(todo)) # rubocop:disable GitlabSecurity/PublicSend
end
def todo_target_anchor(todo)
"note_#{todo.note_id}" if todo.note_id?
end
end end
class NamespaceBasic < Grape::Entity class NamespaceBasic < Grape::Entity
......
...@@ -81,3 +81,5 @@ module Gitlab ...@@ -81,3 +81,5 @@ module Gitlab
end end
end end
end end
::Gitlab::UrlBuilder.prepend_if_ee('EE::Gitlab::UrlBuilder')
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