Commit 8eab7b08 authored by Jarka Kadlecova's avatar Jarka Kadlecova

Prepare backend for assigning issues to epics

parent 50298bcc
...@@ -228,6 +228,7 @@ module IssuablesHelper ...@@ -228,6 +228,7 @@ module IssuablesHelper
if parent.is_a?(Group) if parent.is_a?(Group)
data[:groupPath] = parent.path data[:groupPath] = parent.path
data[:issueLinksEndpoint] = group_epic_issues_path(parent, issuable)
else else
data.merge!(projectPath: ref_project.path, projectNamespace: ref_project.namespace.full_path) data.merge!(projectPath: ref_project.path, projectNamespace: ref_project.namespace.full_path)
end end
......
...@@ -38,6 +38,9 @@ class Issue < ActiveRecord::Base ...@@ -38,6 +38,9 @@ class Issue < ActiveRecord::Base
has_many :issue_assignees has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees has_many :assignees, class_name: "User", through: :issue_assignees
has_one :epic_issue
has_one :epic, through: :epic_issue
validates :project, presence: true validates :project, presence: true
scope :in_projects, ->(project_ids) { where(project_id: project_ids) } scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
......
...@@ -78,6 +78,8 @@ constraints(GroupUrlConstrainer.new) do ...@@ -78,6 +78,8 @@ constraints(GroupUrlConstrainer.new) do
member do member do
get :realtime_changes get :realtime_changes
end end
resources :epic_issues, only: [:index, :create, :destroy], as: 'issues', path: 'issues'
end end
legacy_ee_group_boards_redirect = redirect do |params, request| legacy_ee_group_boards_redirect = redirect do |params, request|
......
class CreateEpicIssuesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :epic_issues do |t|
t.references :epic, null: false, index: true, foreign_key: true
t.references :issue, null: false, index: { unique: true }, foreign_key: true
t.timestamps_with_timezone
end
end
def down
drop_table :epic_issues
end
end
...@@ -732,6 +732,16 @@ ActiveRecord::Schema.define(version: 20171107144726) do ...@@ -732,6 +732,16 @@ ActiveRecord::Schema.define(version: 20171107144726) do
add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", unique: true, using: :btree add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", unique: true, using: :btree
add_index "environments", ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true, using: :btree add_index "environments", ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true, using: :btree
create_table "epic_issues", force: :cascade do |t|
t.integer "epic_id", null: false
t.integer "issue_id", null: false
t.datetime_with_timezone "created_at"
t.datetime_with_timezone "updated_at"
end
add_index "epic_issues", ["epic_id"], name: "index_epic_issues_on_epic_id", using: :btree
add_index "epic_issues", ["issue_id"], name: "index_epic_issues_on_issue_id", unique: true, using: :btree
create_table "epic_metrics", force: :cascade do |t| create_table "epic_metrics", force: :cascade do |t|
t.integer "epic_id", null: false t.integer "epic_id", null: false
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
...@@ -2393,6 +2403,8 @@ ActiveRecord::Schema.define(version: 20171107144726) do ...@@ -2393,6 +2403,8 @@ ActiveRecord::Schema.define(version: 20171107144726) do
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
add_foreign_key "epic_issues", "epics"
add_foreign_key "epic_issues", "issues"
add_foreign_key "epic_metrics", "epics", on_delete: :cascade add_foreign_key "epic_metrics", "epics", on_delete: :cascade
add_foreign_key "epics", "milestones", on_delete: :nullify add_foreign_key "epics", "milestones", on_delete: :nullify
add_foreign_key "epics", "namespaces", column: "group_id", name: "fk_f081aa4489", on_delete: :cascade add_foreign_key "epics", "namespaces", column: "group_id", name: "fk_f081aa4489", on_delete: :cascade
......
module IssuableLinks
def index
render json: issues
end
def create
result = create_service.execute
render json: { message: result[:message], issues: issues }, status: result[:http_status]
end
def destroy
result = destroy_service.execute
render json: { issues: issues }, status: result[:http_status]
end
private
def create_params
params.slice(:issue_references)
end
def create_service
raise NotImplementedError
end
def destroy_service
raise NotImplementedError
end
end
class Groups::EpicIssuesController < Groups::EpicsController
include IssuableLinks
skip_before_action :authorize_destroy_issuable!
before_action :authorize_admin_epic!, only: [:create, :destroy]
private
def create_service
EpicIssues::CreateService.new(epic, current_user, create_params)
end
def destroy_service
epic_issue = EpicIssue.find(params[:id])
EpicIssues::DestroyService.new(epic_issue, current_user)
end
def issues
EpicIssues::ListService.new(epic, current_user).execute
end
def authorize_admin_epic!
render_403 unless can?(current_user, :admin_epic, epic)
end
end
...@@ -9,7 +9,7 @@ class Groups::EpicsController < Groups::ApplicationController ...@@ -9,7 +9,7 @@ class Groups::EpicsController < Groups::ApplicationController
private private
def epic def epic
@issuable = @epic ||= @group.epics.find_by(iid: params[:id]) @issuable = @epic ||= @group.epics.find_by(iid: params[:epic_id] || params[:id])
return render_404 unless can?(current_user, :read_epic, @epic) return render_404 unless can?(current_user, :read_epic, @epic)
......
module Projects module Projects
class IssueLinksController < Projects::ApplicationController class IssueLinksController < Projects::ApplicationController
include IssuableLinks
before_action :authorize_admin_issue_link!, only: [:create, :destroy] before_action :authorize_admin_issue_link!, only: [:create, :destroy]
def index
render json: issues
end
def create
create_params = params.slice(:issue_references)
result = IssueLinks::CreateService.new(issue, current_user, create_params).execute
render json: { message: result[:message], issues: issues }, status: result[:http_status]
end
def destroy
issue_link = IssueLink.find(params[:id])
result = IssueLinks::DestroyService.new(issue_link, current_user).execute
render json: { issues: issues }, status: result[:http_status]
end
private private
def issues def issues
...@@ -36,5 +19,14 @@ module Projects ...@@ -36,5 +19,14 @@ module Projects
.execute .execute
.find_by!(iid: params[:issue_id]) .find_by!(iid: params[:issue_id])
end end
def create_service
IssueLinks::CreateService.new(issue, current_user, create_params)
end
def destroy_service
issue_link = IssueLink.find(params[:id])
IssueLinks::DestroyService.new(issue_link, current_user)
end
end end
end end
...@@ -10,6 +10,8 @@ module EE ...@@ -10,6 +10,8 @@ module EE
belongs_to :assignee, class_name: "User" belongs_to :assignee, class_name: "User"
belongs_to :group belongs_to :group
has_many :epic_issues
validates :group, presence: true validates :group, presence: true
end end
...@@ -24,5 +26,13 @@ module EE ...@@ -24,5 +26,13 @@ module EE
def supports_weight? def supports_weight?
false false
end end
def issues(current_user)
related_issues = ::Issue.select('issues.*, epic_issues.id as epic_issue_id')
.joins(:epic_issue)
.where("epic_issues.epic_id = #{id}")
Ability.issues_readable_by_user(related_issues, current_user)
end
end end
end end
class EpicIssue < ActiveRecord::Base
validates :epic, :issue, presence: true
validates :issue, uniqueness: true
belongs_to :epic
belongs_to :issue
end
...@@ -55,6 +55,7 @@ module EE ...@@ -55,6 +55,7 @@ module EE
enable :admin_board enable :admin_board
enable :read_deploy_board enable :read_deploy_board
enable :admin_issue_link enable :admin_issue_link
enable :admin_epic_issue
end end
rule { can?(:developer_access) }.enable :admin_board rule { can?(:developer_access) }.enable :admin_board
......
module EpicIssues
class CreateService < IssuableLinks::CreateService
private
def relate_issues(referenced_issue)
link = EpicIssue.find_or_initialize_by(issue: referenced_issue)
link.epic = issuable
link.save
end
def create_notes?
false
end
def extractor_context
{ group: issuable.group }
end
def linkable_issues(issues)
issues.select { |issue| can?(current_user, :admin_epic_issue, issue) && issue.project.group == issuable.group }
end
end
end
module EpicIssues
class DestroyService < IssuableLinks::DestroyService
private
def create_notes?
false
end
def source
@source ||= link.epic
end
def target
@target ||= link.issue
end
def permission_to_remove_relation?
can?(current_user, :admin_epic_issue, target) && can?(current_user, :admin_epic, source)
end
end
end
module EpicIssues
class ListService < IssuableLinks::ListService
private
def issues
issuable.issues(current_user)
end
def destroy_relation_path(issue)
if can_destroy_issue_link?(issue)
group_epic_issue_path(issuable.group, issuable.iid, issue.epic_issue_id)
end
end
def can_destroy_issue_link?(issue)
Ability.allowed?(current_user, :admin_issue_link, issue) && Ability.allowed?(current_user, :admin_epic, issuable)
end
def reference(issue)
issue.to_reference(full: true)
end
end
end
module IssueLinks module IssuableLinks
class CreateService < BaseService class CreateService < BaseService
def initialize(issue, user, params) attr_reader :issuable, :current_user, :params
@issue, @current_user, @params = issue, user, params.dup
def initialize(issuable, user, params)
@issuable, @current_user, @params = issuable, user, params.dup
end end
def execute def execute
...@@ -17,20 +19,10 @@ module IssueLinks ...@@ -17,20 +19,10 @@ module IssueLinks
def create_issue_links def create_issue_links
referenced_issues.each do |referenced_issue| referenced_issues.each do |referenced_issue|
create_notes(referenced_issue) if relate_issues(referenced_issue) create_notes(referenced_issue) if relate_issues(referenced_issue) && create_notes?
end end
end end
# Returns a Boolean indicating if the Issue was related.
def relate_issues(referenced_issue)
IssueLink.new(source: @issue, target: referenced_issue).save
end
def create_notes(referenced_issue)
SystemNoteService.relate_issue(@issue, referenced_issue, current_user)
SystemNoteService.relate_issue(referenced_issue, @issue, current_user)
end
def referenced_issues def referenced_issues
@referenced_issues ||= begin @referenced_issues ||= begin
target_issue = params[:target_issue] target_issue = params[:target_issue]
...@@ -43,7 +35,7 @@ module IssueLinks ...@@ -43,7 +35,7 @@ module IssueLinks
[] []
end end
issues.select { |issue| can?(current_user, :admin_issue_link, issue) } linkable_issues(issues)
end end
end end
...@@ -51,10 +43,31 @@ module IssueLinks ...@@ -51,10 +43,31 @@ module IssueLinks
issue_references = params[:issue_references] issue_references = params[:issue_references]
text = issue_references.join(' ') text = issue_references.join(' ')
extractor = Gitlab::ReferenceExtractor.new(@issue.project, @current_user) extractor = Gitlab::ReferenceExtractor.new(issuable.project, @current_user)
extractor.analyze(text) extractor.analyze(text, extractor_context)
extractor.issues extractor.issues
end end
def create_notes(referenced_issue)
SystemNoteService.relate_issue(issuable, referenced_issue, current_user)
SystemNoteService.relate_issue(referenced_issue, issuable, current_user)
end
def extractor_context
{}
end
def create_notes?
true
end
def linkable_issues(issues)
raise NotImplementedError
end
def relate_issues(referenced_issue)
raise NotImplementedError
end
end end
end end
module IssueLinks module IssuableLinks
class DestroyService < BaseService class DestroyService < BaseService
def initialize(issue_link, user) attr_reader :link, :current_user
@issue_link = issue_link
def initialize(link, user)
@link = link
@current_user = user @current_user = user
@issue = issue_link.source
@referenced_issue = issue_link.target
end end
def execute def execute
return error('No Issue Link found', 404) unless permission_to_remove_relation? return error('No Issue Link found', 404) unless permission_to_remove_relation?
remove_relation remove_relation
create_notes create_notes if create_notes?
success(message: 'Relation was removed') success(message: 'Relation was removed')
end end
private private
def remove_relation def create_notes
@issue_link.destroy! SystemNoteService.unrelate_issue(source, target, current_user)
SystemNoteService.unrelate_issue(target, source, current_user)
end end
def create_notes def remove_relation
SystemNoteService.unrelate_issue(@issue, @referenced_issue, current_user) link.destroy!
SystemNoteService.unrelate_issue(@referenced_issue, @issue, current_user)
end end
def permission_to_remove_relation? def create_notes?
can?(current_user, :admin_issue_link, @issue) && true
can?(current_user, :admin_issue_link, @referenced_issue)
end end
end end
end end
module IssueLinks module IssuableLinks
class ListService class ListService
include Gitlab::Routing include Gitlab::Routing
def initialize(issue, user) attr_reader :issuable, :current_user
@issue, @current_user, @project = issue, user, issue.project
def initialize(issuable, user)
@issuable, @current_user = issuable, user
end end
def execute def execute
...@@ -12,7 +14,7 @@ module IssueLinks ...@@ -12,7 +14,7 @@ module IssueLinks
id: referenced_issue.id, id: referenced_issue.id,
title: referenced_issue.title, title: referenced_issue.title,
state: referenced_issue.state, state: referenced_issue.state,
reference: referenced_issue.to_reference(@project), reference: reference(referenced_issue),
path: project_issue_path(referenced_issue.project, referenced_issue.iid), path: project_issue_path(referenced_issue.project, referenced_issue.iid),
destroy_relation_path: destroy_relation_path(referenced_issue) destroy_relation_path: destroy_relation_path(referenced_issue)
} }
...@@ -21,26 +23,12 @@ module IssueLinks ...@@ -21,26 +23,12 @@ module IssueLinks
private private
def issues
@issue.related_issues(@current_user, preload: { project: :namespace })
end
def destroy_relation_path(issue) def destroy_relation_path(issue)
# Make sure the user can admin both the current issue AND the raise NotImplementedError
# referenced issue projects in order to return the removal link.
if can_destroy_issue_link_on_current_project? && can_destroy_issue_link?(issue.project)
project_issue_link_path(@project, @issue.iid, issue.issue_link_id)
end
end
def can_destroy_issue_link_on_current_project?
return @can_destroy_on_current_project if defined?(@can_destroy_on_current_project)
@can_destroy_on_current_project = can_destroy_issue_link?(@project)
end end
def can_destroy_issue_link?(project) def reference(issue)
Ability.allowed?(@current_user, :admin_issue_link, project) issue.to_reference(issuable.project)
end end
end end
end end
module IssueLinks
class CreateService < IssuableLinks::CreateService
def relate_issues(referenced_issue)
IssueLink.new(source: issuable, target: referenced_issue).save
end
def linkable_issues(issues)
issues.select { |issue| can?(current_user, :admin_issue_link, issue) }
end
end
end
module IssueLinks
class DestroyService < IssuableLinks::DestroyService
private
def source
@source ||= link.source
end
def target
@target ||= link.target
end
def permission_to_remove_relation?
can?(current_user, :admin_issue_link, source) && can?(current_user, :admin_issue_link, target)
end
end
end
module IssueLinks
class ListService < IssuableLinks::ListService
private
def issues
issuable.related_issues(current_user, preload: { project: :namespace })
end
def destroy_relation_path(issue)
current_project = issuable.project
# Make sure the user can admin both the current issue AND the
# referenced issue projects in order to return the removal link.
if can_destroy_issue_link_on_current_project?(current_project) && can_destroy_issue_link?(issue.project)
project_issue_link_path(current_project, issuable.iid, issue.issue_link_id)
end
end
def can_destroy_issue_link_on_current_project?(current_project)
return @can_destroy_on_current_project if defined?(@can_destroy_on_current_project)
@can_destroy_on_current_project = can_destroy_issue_link?(current_project)
end
def can_destroy_issue_link?(project)
Ability.allowed?(current_user, :admin_issue_link, project)
end
end
end
...@@ -6,8 +6,7 @@ module Banzai ...@@ -6,8 +6,7 @@ module Banzai
def nodes_visible_to_user(user, nodes) def nodes_visible_to_user(user, nodes)
issues = issues_for_nodes(nodes) issues = issues_for_nodes(nodes)
readable_issues = Ability readable_issues = Ability.issues_readable_by_user(issues.values, user).to_set
.issues_readable_by_user(issues.values, user).to_set
nodes.select do |node| nodes.select do |node|
readable_issues.include?(issues[node]) readable_issues.include?(issues[node])
......
require 'spec_helper'
describe Groups::EpicIssuesController do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
let(:epic) { create(:epic, group: group) }
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
before do
sign_in(user)
end
describe 'GET #index' do
let!(:epic_issues) { create(:epic_issue, epic: epic, issue: issue) }
before do
group.add_developer(user)
get :index, group_id: group, epic_id: epic.to_param
end
it 'returns status 200' do
expect(response.status).to eq(200)
end
it 'returns the correct json' do
expected_result = [
{
'id' => issue.id,
'title' => issue.title,
'state' => issue.state,
'reference' => "#{project.full_path}##{issue.iid}",
'path' => "/#{project.full_path}/issues/#{issue.iid}",
'destroy_relation_path' => "/groups/#{group.full_path}/-/epics/#{epic.iid}/issues/#{epic_issues.id}"
}
]
expect(JSON.parse(response.body)).to eq(expected_result)
end
end
describe 'POST #create' do
subject do
reference = [issue.to_reference(full: true)]
post :create, group_id: group, epic_id: epic.to_param, issue_references: reference
end
context 'when user has permissions to create requested associtaion' do
before do
group.add_developer(user)
end
it 'returns correct response for the correct issue reference' do
subject
list_service_response = EpicIssues::ListService.new(epic, user).execute
expect(response).to have_gitlab_http_status(200)
expect(json_response).to eq('message' => nil, 'issues' => list_service_response.as_json)
end
it 'creates a new EpicIssue record' do
expect { subject }.to change { EpicIssue.count }.from(0).to(1)
end
end
context 'when user does not have permissions to create requested associtaion' do
it 'returns correct response for the correct issue reference' do
subject
expect(response).to have_gitlab_http_status(403)
end
it 'does not create a new EpicIssue record' do
expect { subject }.not_to change { EpicIssue.count }.from(0)
end
end
end
describe 'DELETE #destroy' do
let!(:epic_issue) { create(:epic_issue, epic: epic, issue: issue) }
subject do
delete :destroy, group_id: group, epic_id: epic.to_param, id: epic_issue.id
end
context 'when user has permissions to detele the link' do
before do
group.add_developer(user)
end
it 'returns status 200' do
subject
expect(response.status).to eq(200)
end
it 'destroys the link' do
expect { subject }.to change { EpicIssue.count }.from(1).to(0)
end
end
context 'when user does not have permissions to delete the link' do
it 'returns status 404' do
subject
expect(response.status).to eq(403)
end
it 'does not destroy the link' do
expect { subject }.not_to change { EpicIssue.count }.from(1)
end
end
context 'when the epic_issue record does not exixst' do
it 'returns status 404' do
delete :destroy, group_id: group, epic_id: epic.to_param, id: 9999
expect(response.status).to eq(403)
end
end
end
end
...@@ -5,6 +5,7 @@ describe Epic do ...@@ -5,6 +5,7 @@ describe Epic do
subject { build(:epic) } subject { build(:epic) }
it { is_expected.to belong_to(:author).class_name('User') } it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to belong_to(:assignee).class_name('User') }
it { is_expected.to belong_to(:group) } it { is_expected.to belong_to(:group) }
end end
...@@ -21,4 +22,40 @@ describe Epic do ...@@ -21,4 +22,40 @@ describe Epic do
it { is_expected.to include_module(InternalId) } it { is_expected.to include_module(InternalId) }
end end
describe '#issues' do
let(:user) { create(:user) }
let(:group) { create(:group, :private) }
let(:project) { create(:project, group: group) }
let(:project2) { create(:project, group: group) }
let!(:epic) { create(:epic, group: group) }
let!(:issue) { create(:issue, project: project)}
let!(:lone_issue) { create(:issue, project: project)}
let!(:other_issue) { create(:issue, project: project2)}
let!(:epic_issues) do
[
create(:epic_issue, epic: epic, issue: issue),
create(:epic_issue, epic: epic, issue: other_issue)
]
end
subject { epic.issues(user) }
it 'returns all issues if a user has access to them' do
group.add_developer(user)
expect(subject.count).to eq(2)
expect(subject.map(&:id)).to match_array([issue.id, other_issue.id])
expect(subject.map(&:epic_issue_id)).to match_array(epic_issues.map(&:id))
end
it 'does not return issues user can not see' do
project.add_developer(user)
expect(subject.count).to eq(1)
expect(subject.map(&:id)).to match_array([issue.id])
expect(subject.map(&:epic_issue_id)).to match_array([epic_issues.first.id])
end
end
end end
require 'spec_helper'
describe EpicIssues::CreateService do
describe '#execute' do
let(:group) { create :group }
let(:epic) { create :epic, group: group }
let(:project) { create(:project, group: group) }
let(:issue) { create :issue, project: project }
let(:user) { create :user }
let(:reference) { issue.to_reference(full: true) }
let(:params) do
{}
end
subject { described_class.new(epic, user, params).execute }
context 'when user has permissions to link the issue' do
before do
group.add_developer(user)
end
context 'when the reference list is empty' do
let(:params) do
{ issue_references: [] }
end
it 'returns error' do
is_expected.to eq(message: 'No Issue found for given params', status: :error, http_status: 404)
end
end
context 'when there is an issue to relate' do
context 'when shortcut for Issue is given' do
let(:params) do
{ issue_references: [issue.to_reference] }
end
it 'returns error' do
is_expected.to eq(message: 'No Issue found for given params', status: :error, http_status: 404)
end
it 'no relationship is created' do
expect { subject }.not_to change { EpicIssue.count }
end
end
context 'when a full reference is given' do
let(:params) do
{ issue_references: [reference] }
end
it 'creates relationships' do
expect { subject }.to change(EpicIssue, :count).from(0).to(1)
expect(EpicIssue.find_by!(issue_id: issue.id)).to have_attributes(epic: epic)
end
it 'returns success status' do
is_expected.to eq(status: :success)
end
end
context 'when an issue links is given' do
let(:params) do
{ issue_references: [IssuesHelper.url_for_issue(issue.iid, issue.project)] }
end
it 'creates relationships' do
expect { subject }.to change(EpicIssue, :count).from(0).to(1)
expect(EpicIssue.find_by!(issue_id: issue.id)).to have_attributes(epic: epic)
end
it 'returns success status' do
is_expected.to eq(status: :success)
end
end
end
end
context 'when user does not have permissions to link the issue' do
let(:params) do
{ issue_references: [reference] }
end
it 'returns error' do
is_expected.to eq(message: 'No Issue found for given params', status: :error, http_status: 404)
end
it 'no relationship is created' do
expect { subject }.not_to change { EpicIssue.count }
end
end
context 'when an issue is already assigned to another epic' do
let(:params) do
{ issue_references: [reference] }
end
before do
group.add_developer(user)
create(:epic_issue, epic: epic, issue: issue)
end
let(:another_epic) { create(:epic, group: group) }
subject { described_class.new(another_epic, user, params).execute }
it 'does not create a new association' do
expect { subject }.not_to change(EpicIssue, :count).from(1)
end
it 'updates the existing association' do
expect { subject }.to change { EpicIssue.find_by!(issue_id: issue.id).epic }.from(epic).to(another_epic)
end
it 'returns success status' do
is_expected.to eq(status: :success)
end
end
context 'when issue from non group project is given' do
let(:another_issue) { create :issue }
let(:params) do
{ issue_references: [another_issue.to_reference(full: true)] }
end
before do
group.add_developer(user)
another_issue.project.add_developer(user)
end
it 'returns error' do
is_expected.to eq(message: 'No Issue found for given params', status: :error, http_status: 404)
end
it 'no relationship is created' do
expect { subject }.not_to change { EpicIssue.count }
end
end
end
end
require 'spec_helper'
describe EpicIssues::DestroyService do
describe '#execute' do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:project, group: group) }
let(:epic) { create(:epic, group: group) }
let(:issue) { create(:issue, project: project) }
let!(:epic_issue) { create(:epic_issue, epic: epic, issue: issue) }
subject { described_class.new(epic_issue, user).execute }
context 'when user has permissions to remove associations' do
before do
group.add_reporter(user)
end
it 'removes related issue' do
expect { subject }.to change { EpicIssue.count }.from(1).to(0)
end
it 'returns success message' do
is_expected.to eq(message: 'Relation was removed', status: :success)
end
end
context 'user does not have permissions to remove associations' do
it 'does not remove relation' do
expect { subject }.not_to change { EpicIssue.count }.from(1)
end
it 'returns error message' do
is_expected.to eq(message: 'No Issue Link found', status: :error, http_status: 404)
end
end
end
end
require 'spec_helper'
describe EpicIssues::ListService do
let(:user) { create :user }
let(:group) { create(:group, :private) }
let(:project) { create(:project_empty_repo, group: group) }
let(:other_project) { create(:project_empty_repo, group: group) }
let(:epic) { create(:epic, group: group) }
let(:issue1) { create :issue, project: project }
let(:issue2) { create :issue, project: project }
let(:issue3) { create :issue, project: other_project }
let!(:epic_issue1) { create(:epic_issue, issue: issue1, epic: epic) }
let!(:epic_issue2) { create(:epic_issue, issue: issue2, epic: epic) }
let!(:epic_issue3) { create(:epic_issue, issue: issue3, epic: epic) }
describe '#execute' do
subject { described_class.new(epic, user).execute }
context 'user can see all issues and destroy their associations' do
before do
group.add_developer(user)
end
it 'returns related issues JSON' do
expected_result = [
{
id: issue1.id,
title: issue1.title,
state: issue1.state,
reference: issue1.to_reference(full: true),
path: "/#{project.full_path}/issues/#{issue1.iid}",
destroy_relation_path: "/groups/#{group.full_path}/-/epics/#{epic.iid}/issues/#{epic_issue1.id}"
},
{
id: issue2.id,
title: issue2.title,
state: issue2.state,
reference: issue2.to_reference(full: true),
path: "/#{project.full_path}/issues/#{issue2.iid}",
destroy_relation_path: "/groups/#{group.full_path}/-/epics/#{epic.iid}/issues/#{epic_issue2.id}"
},
{
id: issue3.id,
title: issue3.title,
state: issue3.state,
reference: issue3.to_reference(full: true),
path: "/#{other_project.full_path}/issues/#{issue3.iid}",
destroy_relation_path: "/groups/#{group.full_path}/-/epics/#{epic.iid}/issues/#{epic_issue3.id}"
}
]
expect(subject).to match_array(expected_result)
end
end
context 'user can see only some issues' do
before do
project.add_developer(user)
end
it 'returns related issues JSON' do
expected_result = [
{
id: issue1.id,
title: issue1.title,
state: issue1.state,
reference: issue1.to_reference(full: true),
path: "/#{project.full_path}/issues/#{issue1.iid}",
destroy_relation_path: nil
},
{
id: issue2.id,
title: issue2.title,
state: issue2.state,
reference: issue2.to_reference(full: true),
path: "/#{project.full_path}/issues/#{issue2.iid}",
destroy_relation_path: nil
}
]
expect(subject).to match_array(expected_result)
end
end
end
end
FactoryGirl.define do
factory :epic_issue do
epic
issue
end
end
...@@ -197,6 +197,7 @@ describe IssuablesHelper do ...@@ -197,6 +197,7 @@ describe IssuablesHelper do
expected_data = { expected_data = {
'endpoint' => "/groups/#{@group.full_path}/-/epics/#{epic.iid}", 'endpoint' => "/groups/#{@group.full_path}/-/epics/#{epic.iid}",
'issueLinksEndpoint' => "/groups/#{@group.full_path}/-/epics/#{epic.iid}/links",
'canUpdate' => true, 'canUpdate' => true,
'canDestroy' => true, 'canDestroy' => true,
'issuableRef' => nil, 'issuableRef' => nil,
......
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