Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
521813f2
Commit
521813f2
authored
Jun 07, 2017
by
Oswaldo Ferreira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add related issues backend
parent
113666a8
Changes
23
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
872 additions
and
5 deletions
+872
-5
app/controllers/projects/issue_links_controller.rb
app/controllers/projects/issue_links_controller.rb
+43
-0
app/models/issue_link.rb
app/models/issue_link.rb
+19
-0
app/models/license.rb
app/models/license.rb
+4
-1
app/models/system_note_metadata.rb
app/models/system_note_metadata.rb
+1
-1
app/policies/ee/project_policy.rb
app/policies/ee/project_policy.rb
+5
-0
app/policies/project_policy.rb
app/policies/project_policy.rb
+9
-0
app/services/issue_links/create_service.rb
app/services/issue_links/create_service.rb
+47
-0
app/services/issue_links/destroy_service.rb
app/services/issue_links/destroy_service.rb
+28
-0
app/services/issue_links/list_service.rb
app/services/issue_links/list_service.rb
+58
-0
app/services/system_note_service.rb
app/services/system_note_service.rb
+32
-0
changelogs/unreleased-ee/2001-related-issues.yml
changelogs/unreleased-ee/2001-related-issues.yml
+4
-0
config/locales/en.yml
config/locales/en.yml
+5
-0
config/routes/project.rb
config/routes/project.rb
+2
-0
db/migrate/20170517162048_create_issue_links_table.rb
db/migrate/20170517162048_create_issue_links_table.rb
+25
-0
db/schema.rb
db/schema.rb
+13
-0
spec/factories/issue_links.rb
spec/factories/issue_links.rb
+6
-0
spec/models/issue_link_spec.rb
spec/models/issue_link_spec.rb
+43
-0
spec/policies/project_policy_spec.rb
spec/policies/project_policy_spec.rb
+7
-3
spec/requests/projects/issue_links_controller_spec.rb
spec/requests/projects/issue_links_controller_spec.rb
+136
-0
spec/services/issue_links/create_service_spec.rb
spec/services/issue_links/create_service_spec.rb
+131
-0
spec/services/issue_links/destroy_service_spec.rb
spec/services/issue_links/destroy_service_spec.rb
+28
-0
spec/services/issue_links/list_service_spec.rb
spec/services/issue_links/list_service_spec.rb
+194
-0
spec/services/system_note_service_spec.rb
spec/services/system_note_service_spec.rb
+32
-0
No files found.
app/controllers/projects/issue_links_controller.rb
0 → 100644
View file @
521813f2
module
Projects
class
IssueLinksController
<
Projects
::
ApplicationController
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
])
return
render_403
unless
can?
(
current_user
,
:admin_issue_link
,
issue_link
.
target
.
project
)
IssueLinks
::
DestroyService
.
new
(
issue_link
,
current_user
).
execute
render
json:
{
issues:
issues
}
end
private
def
issues
IssueLinks
::
ListService
.
new
(
issue
,
current_user
).
execute
end
def
authorize_admin_issue_link!
render_403
unless
can?
(
current_user
,
:admin_issue_link
,
@project
)
end
def
issue
@issue
||=
IssuesFinder
.
new
(
current_user
,
project_id:
@project
.
id
)
.
execute
.
find_by!
(
iid:
params
[
:issue_id
])
end
end
end
app/models/issue_link.rb
0 → 100644
View file @
521813f2
class
IssueLink
<
ActiveRecord
::
Base
belongs_to
:source
,
class_name:
'Issue'
belongs_to
:target
,
class_name:
'Issue'
validates
:source
,
presence:
true
validates
:target
,
presence:
true
validates
:source
,
uniqueness:
{
scope: :target_id
,
message:
'is already related'
}
validate
:check_self_relation
private
def
check_self_relation
return
unless
source
&&
target
if
source
==
target
errors
.
add
(
:source
,
'cannot be related to itself'
)
end
end
end
app/models/license.rb
View file @
521813f2
...
...
@@ -8,6 +8,7 @@ class License < ActiveRecord::Base
SERVICE_DESK_FEATURE
=
'GitLab_ServiceDesk'
.
freeze
OBJECT_STORAGE_FEATURE
=
'GitLab_ObjectStorage'
.
freeze
ELASTIC_SEARCH_FEATURE
=
'GitLab_ElasticSearch'
.
freeze
RELATED_ISSUES_FEATURE
=
'RelatedIssues'
.
freeze
FEATURE_CODES
=
{
geo:
GEO_FEATURE
,
...
...
@@ -15,6 +16,7 @@ class License < ActiveRecord::Base
service_desk:
SERVICE_DESK_FEATURE
,
object_storage:
OBJECT_STORAGE_FEATURE
,
elastic_search:
ELASTIC_SEARCH_FEATURE
,
related_issues:
RELATED_ISSUES_FEATURE
,
# Features that make sense to Namespace:
deploy_board:
DEPLOY_BOARD_FEATURE
,
file_lock:
FILE_LOCK_FEATURE
...
...
@@ -26,7 +28,8 @@ class License < ActiveRecord::Base
EARLY_ADOPTER_PLAN
=
'early_adopter'
.
freeze
EES_FEATURES
=
[
{
ELASTIC_SEARCH_FEATURE
=>
1
}
{
ELASTIC_SEARCH_FEATURE
=>
1
},
{
RELATED_ISSUES_FEATURE
=>
1
}
].
freeze
EEP_FEATURES
=
[
...
...
app/models/system_note_metadata.rb
View file @
521813f2
...
...
@@ -3,7 +3,7 @@ class SystemNoteMetadata < ActiveRecord::Base
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved opened closed merged
outdated
approved unapproved
approved unapproved
relate unrelate
]
.
freeze
validates
:note
,
presence:
true
...
...
app/policies/ee/project_policy.rb
View file @
521813f2
...
...
@@ -22,6 +22,11 @@ module EE
cannot!
:create_note
cannot!
:read_project
end
unless
project
.
feature_available?
(
:related_issues
)
cannot!
:read_issue_link
cannot!
:admin_issue_link
end
end
end
end
app/policies/project_policy.rb
View file @
521813f2
...
...
@@ -55,6 +55,9 @@ class ProjectPolicy < BasePolicy
can!
:read_pipeline_schedule
can!
:read_build
end
# EE-only
can!
:read_issue_link
end
def
reporter_access!
...
...
@@ -79,6 +82,9 @@ class ProjectPolicy < BasePolicy
if
project
.
feature_available?
(
:deploy_board
)
||
Rails
.
env
.
development?
can!
:read_deploy_board
end
# EE-only
can!
:admin_issue_link
end
# Permissions given when an user is team member of a project
...
...
@@ -321,5 +327,8 @@ class ProjectPolicy < BasePolicy
# NOTE: may be overridden by IssuePolicy
can!
:read_issue
# EE-only
can!
:read_issue_link
end
end
app/services/issue_links/create_service.rb
0 → 100644
View file @
521813f2
module
IssueLinks
class
CreateService
<
BaseService
def
initialize
(
issue
,
user
,
params
)
@issue
,
@current_user
,
@params
=
issue
,
user
,
params
.
dup
end
def
execute
if
referenced_issues
.
blank?
return
error
(
'No Issue found for given reference'
,
401
)
end
create_issue_links
success
end
private
def
create_issue_links
referenced_issues
.
each
do
|
referenced_issue
|
create_notes
(
referenced_issue
)
if
relate_issues
(
referenced_issue
)
end
end
def
relate_issues
(
referenced_issue
)
IssueLink
.
create
(
source:
@issue
,
target:
referenced_issue
)
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
@referenced_issues
||=
begin
issue_references
=
params
[
:issue_references
]
text
=
issue_references
.
join
(
' '
)
extractor
=
Gitlab
::
ReferenceExtractor
.
new
(
@issue
.
project
,
@current_user
)
extractor
.
analyze
(
text
)
extractor
.
issues
.
select
do
|
issue
|
can?
(
current_user
,
:admin_issue_link
,
issue
)
end
end
end
end
end
app/services/issue_links/destroy_service.rb
0 → 100644
View file @
521813f2
module
IssueLinks
class
DestroyService
<
BaseService
def
initialize
(
issue_link
,
user
)
@issue_link
=
issue_link
@current_user
=
user
@issue
=
issue_link
.
source
@referenced_issue
=
issue_link
.
target
end
def
execute
remove_relation
create_notes
success
(
message:
'Relation was removed'
)
end
private
def
remove_relation
@issue_link
.
destroy!
end
def
create_notes
SystemNoteService
.
unrelate_issue
(
@issue
,
@referenced_issue
,
current_user
)
SystemNoteService
.
unrelate_issue
(
@referenced_issue
,
@issue
,
current_user
)
end
end
end
app/services/issue_links/list_service.rb
0 → 100644
View file @
521813f2
module
IssueLinks
class
ListService
include
Gitlab
::
Routing
def
initialize
(
issue
,
user
)
@issue
,
@current_user
,
@project
=
issue
,
user
,
issue
.
project
end
def
execute
issues
.
map
do
|
referenced_issue
|
{
id:
referenced_issue
.
id
,
title:
referenced_issue
.
title
,
state:
referenced_issue
.
state
,
reference:
referenced_issue
.
to_reference
(
@project
),
path:
namespace_project_issue_path
(
referenced_issue
.
project
.
namespace
,
referenced_issue
.
project
,
referenced_issue
.
iid
),
destroy_relation_path:
destroy_relation_path
(
referenced_issue
)
}
end
end
private
def
issues
related_issues
=
Issue
.
select
([
'issues.*'
,
'issue_links.id AS issue_links_id'
])
.
joins
(
"INNER JOIN issue_links ON
(issue_links.source_id = issues.id AND issue_links.target_id =
#{
@issue
.
id
}
)
OR
(issue_links.target_id = issues.id AND issue_links.source_id =
#{
@issue
.
id
}
)"
)
.
preload
(
project: :namespace
)
.
reorder
(
'issue_links_id'
)
Ability
.
issues_readable_by_user
(
related_issues
,
@current_user
)
end
def
destroy_relation_path
(
issue
)
# 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?
&&
can_destroy_issue_link?
(
issue
.
project
)
namespace_project_issue_link_path
(
@project
.
namespace
,
@issue
.
project
,
@issue
.
iid
,
issue
.
issue_links_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
def
can_destroy_issue_link?
(
project
)
Ability
.
allowed?
(
@current_user
,
:admin_issue_link
,
project
)
end
end
end
app/services/system_note_service.rb
View file @
521813f2
...
...
@@ -552,6 +552,38 @@ module SystemNoteService
create_note
(
NoteSummary
.
new
(
noteable
,
project
,
author
,
body
,
action:
'moved'
))
end
#
# noteable - Noteable object
# noteable_ref - Referenced noteable object
# user - User performing reference
#
# Example Note text:
#
# "marked this issue as related to gitlab-ce#9001"
#
# Returns the created Note object
def
relate_issue
(
noteable
,
noteable_ref
,
user
)
body
=
"marked this issue as related to
#{
noteable_ref
.
to_reference
(
noteable
.
project
)
}
"
create_note
(
NoteSummary
.
new
(
noteable
,
noteable
.
project
,
user
,
body
,
action:
'relate'
))
end
#
# noteable - Noteable object
# noteable_ref - Referenced noteable object
# user - User performing reference
#
# Example Note text:
#
# "removed the relation with gitlab-ce#9001"
#
# Returns the created Note object
def
unrelate_issue
(
noteable
,
noteable_ref
,
user
)
body
=
"removed the relation with
#{
noteable_ref
.
to_reference
(
noteable
.
project
)
}
"
create_note
(
NoteSummary
.
new
(
noteable
,
noteable
.
project
,
user
,
body
,
action:
'unrelate'
))
end
# Called when the merge request is approved by user
#
# noteable - Noteable object
...
...
changelogs/unreleased-ee/2001-related-issues.yml
0 → 100644
View file @
521813f2
---
title
:
Allows manually adding bi-directional relationships between issues in the issue page (EES feature)
merge_request
:
author
:
config/locales/en.yml
View file @
521813f2
...
...
@@ -3,6 +3,11 @@
en
:
hello
:
"
Hello
world"
activerecord
:
attributes
:
issue_link
:
source
:
Source issue
target
:
Target issue
errors
:
messages
:
label_already_exists_at_group_level
:
"
already
exists
at
group
level
for
%{group}.
Please
choose
another
one."
...
...
config/routes/project.rb
View file @
521813f2
...
...
@@ -311,6 +311,8 @@ constraints(ProjectUrlConstrainer.new) do
post
:bulk_update
post
:export_csv
end
resources
:issue_links
,
only:
[
:index
,
:create
,
:destroy
],
as:
'links'
,
path:
'links'
end
resources
:project_members
,
except:
[
:show
,
:new
,
:edit
],
constraints:
{
id:
/[a-zA-Z.\/0-9_\-#%+]+/
},
concerns: :access_requestable
do
...
...
db/migrate/20170517162048_create_issue_links_table.rb
0 → 100644
View file @
521813f2
class
CreateIssueLinksTable
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
disable_ddl_transaction!
def
up
create_table
:issue_links
do
|
t
|
t
.
integer
:source_id
,
null:
false
,
index:
true
t
.
integer
:target_id
,
null:
false
,
index:
true
t
.
timestamps
null:
true
end
add_index
:issue_links
,
[
:source_id
,
:target_id
],
unique:
true
add_concurrent_foreign_key
:issue_links
,
:issues
,
column: :source_id
add_concurrent_foreign_key
:issue_links
,
:issues
,
column: :target_id
end
def
down
drop_table
:issue_links
end
end
db/schema.rb
View file @
521813f2
...
...
@@ -654,6 +654,17 @@ ActiveRecord::Schema.define(version: 20170606202615) do
add_index
"issue_assignees"
,
[
"issue_id"
,
"user_id"
],
name:
"index_issue_assignees_on_issue_id_and_user_id"
,
unique:
true
,
using: :btree
add_index
"issue_assignees"
,
[
"user_id"
],
name:
"index_issue_assignees_on_user_id"
,
using: :btree
create_table
"issue_links"
,
force: :cascade
do
|
t
|
t
.
integer
"source_id"
,
null:
false
t
.
integer
"target_id"
,
null:
false
t
.
datetime
"created_at"
t
.
datetime
"updated_at"
end
add_index
"issue_links"
,
[
"source_id"
,
"target_id"
],
name:
"index_issue_links_on_source_id_and_target_id"
,
unique:
true
,
using: :btree
add_index
"issue_links"
,
[
"source_id"
],
name:
"index_issue_links_on_source_id"
,
using: :btree
add_index
"issue_links"
,
[
"target_id"
],
name:
"index_issue_links_on_target_id"
,
using: :btree
create_table
"issue_metrics"
,
force: :cascade
do
|
t
|
t
.
integer
"issue_id"
,
null:
false
t
.
datetime
"first_mentioned_in_commit_at"
...
...
@@ -1779,6 +1790,8 @@ ActiveRecord::Schema.define(version: 20170606202615) do
add_foreign_key
"geo_repository_updated_events"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"issue_assignees"
,
"issues"
,
name:
"fk_b7d881734a"
,
on_delete: :cascade
add_foreign_key
"issue_assignees"
,
"users"
,
name:
"fk_5e0c8d9154"
,
on_delete: :cascade
add_foreign_key
"issue_links"
,
"issues"
,
column:
"source_id"
,
name:
"fk_c900194ff2"
,
on_delete: :cascade
add_foreign_key
"issue_links"
,
"issues"
,
column:
"target_id"
,
name:
"fk_e71bb44f1f"
,
on_delete: :cascade
add_foreign_key
"issue_metrics"
,
"issues"
,
on_delete: :cascade
add_foreign_key
"label_priorities"
,
"labels"
,
on_delete: :cascade
add_foreign_key
"label_priorities"
,
"projects"
,
on_delete: :cascade
...
...
spec/factories/issue_links.rb
0 → 100644
View file @
521813f2
FactoryGirl
.
define
do
factory
:issue_link
do
source
factory: :issue
target
factory: :issue
end
end
spec/models/issue_link_spec.rb
0 → 100644
View file @
521813f2
require
'spec_helper'
describe
IssueLink
do
describe
'Associations'
do
it
{
is_expected
.
to
belong_to
(
:source
).
class_name
(
'Issue'
)
}
it
{
is_expected
.
to
belong_to
(
:target
).
class_name
(
'Issue'
)
}
end
describe
'Validation'
do
subject
{
create
:issue_link
}
it
{
is_expected
.
to
validate_presence_of
(
:source
)
}
it
{
is_expected
.
to
validate_presence_of
(
:target
)
}
it
do
is_expected
.
to
validate_uniqueness_of
(
:source
)
.
scoped_to
(
:target_id
)
.
with_message
(
/already related/
)
end
context
'self relation'
do
let
(
:issue
)
{
create
:issue
}
context
'cannot be validated'
do
it
'does not invalidate object with self relation error'
do
issue_link
=
build
:issue_link
,
source:
issue
,
target:
nil
issue_link
.
valid?
expect
(
issue_link
.
errors
[
:source
]).
to
be_empty
end
end
context
'can be invalidated'
do
it
'invalidates object'
do
issue_link
=
build
:issue_link
,
source:
issue
,
target:
issue
expect
(
issue_link
).
to
be_invalid
expect
(
issue_link
.
errors
[
:source
]).
to
include
(
'cannot be related to itself'
)
end
end
end
end
end
spec/policies/project_policy_spec.rb
View file @
521813f2
...
...
@@ -10,10 +10,14 @@ describe ProjectPolicy, models: true do
let
(
:admin
)
{
create
(
:admin
)
}
let
(
:project
)
{
create
(
:empty_project
,
:public
,
namespace:
owner
.
namespace
)
}
before
do
allow_any_instance_of
(
License
).
to
receive
(
:feature_available?
)
{
true
}
end
let
(
:guest_permissions
)
do
%i[
read_project read_board read_list read_wiki read_issue read_label
read_milestone read_project_snippet read_project_member
read_
issue_link read_
milestone read_project_snippet read_project_member
read_note create_project create_issue create_note
upload_file
]
...
...
@@ -22,7 +26,7 @@ describe ProjectPolicy, models: true do
let
(
:reporter_permissions
)
do
%i[
download_code fork_project create_project_snippet update_issue
admin_issue admin_label admin_list read_commit_status read_build
admin_issue admin_label admin_
issue_link admin_
list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
read_merge_request download_wiki_code
]
...
...
@@ -71,7 +75,7 @@ describe ProjectPolicy, models: true do
let
(
:auditor_permissions
)
do
%i[
download_code download_wiki_code read_project read_board read_list
read_wiki read_issue read_label read_milestone read_project_snippet
read_wiki read_issue read_label read_
issue_link read_
milestone read_project_snippet
read_project_member read_note read_cycle_analytics read_pipeline
read_build read_commit_status read_container_image read_environment
read_deployment read_merge_request read_pages
...
...
spec/requests/projects/issue_links_controller_spec.rb
0 → 100644
View file @
521813f2
require
'rails_helper'
describe
Projects
::
IssueLinksController
do
let
(
:user
)
{
create
:user
}
let
(
:project
)
{
create
(
:project_empty_repo
)
}
let
(
:issue
)
{
create
:issue
,
project:
project
}
before
do
allow_any_instance_of
(
License
).
to
receive
(
:feature_available?
)
{
false
}
allow_any_instance_of
(
License
).
to
receive
(
:feature_available?
).
with
(
:related_issues
)
{
true
}
end
describe
'GET /*namespace_id/:project_id/issues/:issue_id/links'
do
let
(
:issue_b
)
{
create
:issue
,
project:
project
}
let!
(
:issue_link
)
{
create
:issue_link
,
source:
issue
,
target:
issue_b
}
before
do
project
.
team
<<
[
user
,
:guest
]
login_as
user
end
it
'returns JSON response'
do
list_service_response
=
IssueLinks
::
ListService
.
new
(
issue
,
user
).
execute
get
namespace_project_issue_links_path
(
issue_links_params
)
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
eq
(
list_service_response
.
as_json
)
end
end
describe
'POST /*namespace_id/:project_id/issues/:issue_id/links'
do
let
(
:issue_b
)
{
create
:issue
,
project:
project
}
before
do
project
.
team
<<
[
user
,
user_role
]
login_as
user
end
context
'with success'
do
let
(
:user_role
)
{
:developer
}
let
(
:issue_references
)
{
[
issue_b
.
to_reference
]
}
it
'returns success JSON'
do
post
namespace_project_issue_links_path
(
issue_links_params
(
issue_references:
issue_references
))
list_service_response
=
IssueLinks
::
ListService
.
new
(
issue
,
user
).
execute
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
).
to
eq
(
'message'
=>
nil
,
'issues'
=>
list_service_response
.
as_json
)
end
end
context
'with failure'
do
context
'when unauthorized'
do
let
(
:user_role
)
{
:guest
}
let
(
:issue_references
)
{
[
issue_b
.
to_reference
]
}
it
'returns 403'
do
post
namespace_project_issue_links_path
(
issue_links_params
(
issue_references:
issue_references
))
expect
(
response
).
to
have_http_status
(
403
)
end
end
context
'when failing service result'
do
let
(
:user_role
)
{
:developer
}
let
(
:issue_references
)
{
[
'#999'
]
}
it
'returns failure JSON'
do
post
namespace_project_issue_links_path
(
issue_links_params
(
issue_references:
issue_references
))
list_service_response
=
IssueLinks
::
ListService
.
new
(
issue
,
user
).
execute
expect
(
response
).
to
have_http_status
(
401
)
expect
(
json_response
).
to
eq
(
'message'
=>
'No Issue found for given reference'
,
'issues'
=>
list_service_response
.
as_json
)
end
end
end
end
describe
'DELETE /*namespace_id/:project_id/issues/:issue_id/link/:id'
do
let
(
:issue_link
)
{
create
:issue_link
,
target:
referenced_issue
}
before
do
project
.
team
<<
[
user
,
user_role
]
login_as
user
end
context
'when unauthorized'
do
context
'when no authorization on current project'
do
let
(
:referenced_issue
)
{
create
:issue
,
project:
project
}
let
(
:user_role
)
{
:guest
}
it
'returns 403'
do
delete
namespace_project_issue_link_path
(
issue_links_params
(
id:
issue_link
.
id
))
expect
(
response
).
to
have_http_status
(
403
)
end
end
context
'when no authorization on the related issue project'
do
# unauthorized project issue
let
(
:referenced_issue
)
{
create
:issue
}
let
(
:user_role
)
{
:developer
}
it
'returns 403'
do
delete
namespace_project_issue_link_path
(
issue_links_params
(
id:
issue_link
.
id
))
expect
(
response
).
to
have_http_status
(
403
)
end
end
end
context
'when authorized'
do
let
(
:referenced_issue
)
{
create
:issue
,
project:
project
}
let
(
:user_role
)
{
:developer
}
it
'returns success JSON'
do
delete
namespace_project_issue_link_path
(
issue_links_params
(
id:
issue_link
.
id
))
list_service_response
=
IssueLinks
::
ListService
.
new
(
issue
,
user
).
execute
expect
(
json_response
).
to
eq
(
'issues'
=>
list_service_response
.
as_json
)
end
end
end
def
issue_links_params
(
opts
=
{})
opts
.
reverse_merge
(
namespace_id:
issue
.
project
.
namespace
,
project_id:
issue
.
project
,
issue_id:
issue
,
format: :json
)
end
end
spec/services/issue_links/create_service_spec.rb
0 → 100644
View file @
521813f2
require
'spec_helper'
describe
IssueLinks
::
CreateService
,
service:
true
do
describe
'#execute'
do
let
(
:namespace
)
{
create
:namespace
}
let
(
:project
)
{
create
:empty_project
,
namespace:
namespace
}
let
(
:issue
)
{
create
:issue
,
project:
project
}
let
(
:user
)
{
create
:user
}
let
(
:params
)
do
{}
end
before
do
allow_any_instance_of
(
License
).
to
receive
(
:feature_available?
)
{
false
}
allow_any_instance_of
(
License
).
to
receive
(
:feature_available?
).
with
(
:related_issues
)
{
true
}
project
.
team
<<
[
user
,
:developer
]
end
subject
{
described_class
.
new
(
issue
,
user
,
params
).
execute
}
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 reference'
,
status: :error
,
http_status:
401
)
end
end
context
'when Issue not found'
do
let
(
:params
)
do
{
issue_references:
[
'#999'
]
}
end
it
'returns error'
do
is_expected
.
to
eq
(
message:
'No Issue found for given reference'
,
status: :error
,
http_status:
401
)
end
it
'no relationship is created'
do
expect
{
subject
}.
not_to
change
(
IssueLink
,
:count
)
end
end
context
'when user has no permission to target project Issue'
do
let
(
:target_issue
)
{
create
:issue
}
let
(
:params
)
do
{
issue_references:
[
target_issue
.
to_reference
(
project
)]
}
end
it
'returns error'
do
target_issue
.
project
.
add_guest
(
user
)
is_expected
.
to
eq
(
message:
'No Issue found for given reference'
,
status: :error
,
http_status:
401
)
end
it
'no relationship is created'
do
expect
{
subject
}.
not_to
change
(
IssueLink
,
:count
)
end
end
context
'when there is an issue to relate'
do
let
(
:issue_a
)
{
create
:issue
,
project:
project
}
let
(
:another_project
)
{
create
:empty_project
,
namespace:
project
.
namespace
}
let
(
:another_project_issue
)
{
create
:issue
,
project:
another_project
}
let
(
:issue_a_ref
)
{
issue_a
.
to_reference
}
let
(
:another_project_issue_ref
)
{
another_project_issue
.
to_reference
(
project
)
}
let
(
:params
)
do
{
issue_references:
[
issue_a_ref
,
another_project_issue_ref
]
}
end
before
do
another_project
.
team
<<
[
user
,
:developer
]
end
it
'creates relationships'
do
expect
{
subject
}.
to
change
(
IssueLink
,
:count
).
from
(
0
).
to
(
2
)
expect
(
IssueLink
.
find_by!
(
target:
issue_a
)).
to
have_attributes
(
source:
issue
)
expect
(
IssueLink
.
find_by!
(
target:
another_project_issue
)).
to
have_attributes
(
source:
issue
)
end
it
'returns success status'
do
is_expected
.
to
eq
(
status: :success
)
end
it
'creates notes'
do
# First two-way relation notes
expect
(
SystemNoteService
).
to
receive
(
:relate_issue
)
.
with
(
issue
,
issue_a
,
user
)
expect
(
SystemNoteService
).
to
receive
(
:relate_issue
)
.
with
(
issue_a
,
issue
,
user
)
# Second two-way relation notes
expect
(
SystemNoteService
).
to
receive
(
:relate_issue
)
.
with
(
issue
,
another_project_issue
,
user
)
expect
(
SystemNoteService
).
to
receive
(
:relate_issue
)
.
with
(
another_project_issue
,
issue
,
user
)
subject
end
end
context
'when reference of any already related issue is present'
do
let
(
:issue_a
)
{
create
:issue
,
project:
project
}
let
(
:issue_b
)
{
create
:issue
,
project:
project
}
before
do
create
:issue_link
,
source:
issue
,
target:
issue_a
end
let
(
:params
)
do
{
issue_references:
[
issue_b
.
to_reference
,
issue_a
.
to_reference
]
}
end
it
'returns success status'
do
is_expected
.
to
eq
(
status: :success
)
end
it
'valid relations are created'
do
expect
{
subject
}.
to
change
(
IssueLink
,
:count
).
from
(
1
).
to
(
2
)
expect
(
IssueLink
.
find_by!
(
target:
issue_b
)).
to
have_attributes
(
source:
issue
)
end
end
end
end
spec/services/issue_links/destroy_service_spec.rb
0 → 100644
View file @
521813f2
require
'spec_helper'
describe
IssueLinks
::
DestroyService
,
service:
true
do
describe
'#execute'
do
let
(
:user
)
{
create
:user
}
let!
(
:issue_link
)
{
create
:issue_link
}
subject
{
described_class
.
new
(
issue_link
,
user
).
execute
}
it
'removes related issue'
do
expect
{
subject
}.
to
change
(
IssueLink
,
:count
).
from
(
1
).
to
(
0
)
end
it
'creates notes'
do
# Two-way notes creation
expect
(
SystemNoteService
).
to
receive
(
:unrelate_issue
)
.
with
(
issue_link
.
source
,
issue_link
.
target
,
user
)
expect
(
SystemNoteService
).
to
receive
(
:unrelate_issue
)
.
with
(
issue_link
.
target
,
issue_link
.
source
,
user
)
subject
end
it
'returns success message'
do
is_expected
.
to
eq
(
message:
'Relation was removed'
,
status: :success
)
end
end
end
spec/services/issue_links/list_service_spec.rb
0 → 100644
View file @
521813f2
require
'spec_helper'
describe
IssueLinks
::
ListService
,
service:
true
do
let
(
:user
)
{
create
:user
}
let
(
:project
)
{
create
(
:project_empty_repo
,
:private
)
}
let
(
:issue
)
{
create
:issue
,
project:
project
}
let
(
:user_role
)
{
:developer
}
before
do
allow_any_instance_of
(
License
).
to
receive
(
:feature_available?
)
{
false
}
allow_any_instance_of
(
License
).
to
receive
(
:feature_available?
).
with
(
:related_issues
)
{
true
}
project
.
team
<<
[
user
,
user_role
]
end
describe
'#execute'
do
subject
{
described_class
.
new
(
issue
,
user
).
execute
}
context
'user can see all issues'
do
let
(
:issue_b
)
{
create
:issue
,
project:
project
}
let
(
:issue_c
)
{
create
:issue
,
project:
project
}
let
(
:issue_d
)
{
create
:issue
,
project:
project
}
let!
(
:issue_link_c
)
do
create
(
:issue_link
,
source:
issue_d
,
target:
issue
)
end
let!
(
:issue_link_b
)
do
create
(
:issue_link
,
source:
issue
,
target:
issue_c
)
end
let!
(
:issue_link_a
)
do
create
(
:issue_link
,
source:
issue
,
target:
issue_b
)
end
it
'ensures no N+1 queries are made'
do
control_count
=
ActiveRecord
::
QueryRecorder
.
new
{
subject
}.
count
project
=
create
:empty_project
,
:public
issue_x
=
create
:issue
,
project:
project
issue_y
=
create
:issue
,
project:
project
issue_z
=
create
:issue
,
project:
project
create
:issue_link
,
source:
issue_x
,
target:
issue_y
create
:issue_link
,
source:
issue_x
,
target:
issue_z
create
:issue_link
,
source:
issue_y
,
target:
issue_z
expect
{
subject
}.
not_to
exceed_query_limit
(
control_count
)
end
it
'returns related issues JSON'
do
expect
(
subject
.
size
).
to
eq
(
3
)
expect
(
subject
).
to
include
(
include
(
id:
issue_b
.
id
,
title:
issue_b
.
title
,
state:
issue_b
.
state
,
reference:
issue_b
.
to_reference
(
project
),
path:
"/
#{
project
.
full_path
}
/issues/
#{
issue_b
.
iid
}
"
,
destroy_relation_path:
"/
#{
project
.
full_path
}
/issues/
#{
issue
.
iid
}
/links/
#{
issue_link_a
.
id
}
"
))
expect
(
subject
).
to
include
(
include
(
id:
issue_c
.
id
,
title:
issue_c
.
title
,
state:
issue_c
.
state
,
reference:
issue_c
.
to_reference
(
project
),
path:
"/
#{
project
.
full_path
}
/issues/
#{
issue_c
.
iid
}
"
,
destroy_relation_path:
"/
#{
project
.
full_path
}
/issues/
#{
issue
.
iid
}
/links/
#{
issue_link_b
.
id
}
"
))
expect
(
subject
).
to
include
(
include
(
id:
issue_d
.
id
,
title:
issue_d
.
title
,
state:
issue_d
.
state
,
reference:
issue_d
.
to_reference
(
project
),
path:
"/
#{
project
.
full_path
}
/issues/
#{
issue_d
.
iid
}
"
,
destroy_relation_path:
"/
#{
project
.
full_path
}
/issues/
#{
issue
.
iid
}
/links/
#{
issue_link_c
.
id
}
"
))
end
end
context
'referencing a public project issue'
do
let
(
:public_project
)
{
create
:empty_project
,
:public
}
let
(
:issue_b
)
{
create
:issue
,
project:
public_project
}
let!
(
:issue_link
)
do
create
(
:issue_link
,
source:
issue
,
target:
issue_b
)
end
it
'presents issue'
do
expect
(
subject
.
size
).
to
eq
(
1
)
end
end
context
'referencing issue with removed relationships'
do
context
'when referenced a deleted issue'
do
let
(
:issue_b
)
{
create
:issue
,
project:
project
}
let!
(
:issue_link
)
do
create
(
:issue_link
,
source:
issue
,
target:
issue_b
)
end
it
'ignores issue'
do
issue_b
.
destroy!
is_expected
.
to
eq
([])
end
end
context
'when referenced an issue with deleted project'
do
let
(
:issue_b
)
{
create
:issue
,
project:
project
}
let!
(
:issue_link
)
do
create
(
:issue_link
,
source:
issue
,
target:
issue_b
)
end
it
'ignores issue'
do
project
.
destroy!
is_expected
.
to
eq
([])
end
end
context
'when referenced an issue with deleted namespace'
do
let
(
:issue_b
)
{
create
:issue
,
project:
project
}
let!
(
:issue_link
)
do
create
(
:issue_link
,
source:
issue
,
target:
issue_b
)
end
it
'ignores issue'
do
project
.
namespace
.
destroy!
is_expected
.
to
eq
([])
end
end
end
context
'user cannot see relations'
do
context
'when user cannot see the referenced issue'
do
let!
(
:issue_link
)
do
create
(
:issue_link
,
source:
issue
)
end
it
'returns an empty list'
do
is_expected
.
to
eq
([])
end
end
context
'when user cannot see the issue that referenced'
do
let!
(
:issue_link
)
do
create
(
:issue_link
,
target:
issue
)
end
it
'returns an empty list'
do
is_expected
.
to
eq
([])
end
end
end
context
'remove relations'
do
let!
(
:issue_link
)
do
create
(
:issue_link
,
source:
issue
,
target:
referenced_issue
)
end
context
'user can admin related issues just on target project'
do
let
(
:user_role
)
{
:guest
}
let
(
:target_project
)
{
create
:empty_project
}
let
(
:referenced_issue
)
{
create
:issue
,
project:
target_project
}
it
'returns no destroy relation path'
do
target_project
.
add_developer
(
user
)
expect
(
subject
.
first
[
:destroy_relation_path
]).
to
be_nil
end
end
context
'user can admin related issues just on source project'
do
let
(
:user_role
)
{
:developer
}
let
(
:target_project
)
{
create
:empty_project
}
let
(
:referenced_issue
)
{
create
:issue
,
project:
target_project
}
it
'returns no destroy relation path'
do
target_project
.
add_guest
(
user
)
expect
(
subject
.
first
[
:destroy_relation_path
]).
to
be_nil
end
end
context
'when user can admin related issues on both projects'
do
let
(
:referenced_issue
)
{
create
:issue
,
project:
project
}
it
'returns related issue destroy relation path'
do
expect
(
subject
.
first
[
:destroy_relation_path
])
.
to
eq
(
"/
#{
project
.
full_path
}
/issues/
#{
issue
.
iid
}
/links/
#{
issue_link
.
id
}
"
)
end
end
end
end
end
spec/services/system_note_service_spec.rb
View file @
521813f2
...
...
@@ -899,6 +899,38 @@ describe SystemNoteService, services: true do
end
end
describe
'.relate_issue'
do
let
(
:noteable_ref
)
{
create
(
:issue
)
}
subject
{
described_class
.
relate_issue
(
noteable
,
noteable_ref
,
author
)
}
it_behaves_like
'a system note'
do
let
(
:action
)
{
'relate'
}
end
context
'when issue marks another as related'
do
it
'sets the note text'
do
expect
(
subject
.
note
).
to
eq
"marked this issue as related to
#{
noteable_ref
.
to_reference
(
project
)
}
"
end
end
end
describe
'.unrelate_issue'
do
let
(
:noteable_ref
)
{
create
(
:issue
)
}
subject
{
described_class
.
unrelate_issue
(
noteable
,
noteable_ref
,
author
)
}
it_behaves_like
'a system note'
do
let
(
:action
)
{
'unrelate'
}
end
context
'when issue relation is removed'
do
it
'sets the note text'
do
expect
(
subject
.
note
).
to
eq
"removed the relation with
#{
noteable_ref
.
to_reference
(
project
)
}
"
end
end
end
describe
'.approve_mr'
do
let
(
:noteable
)
{
create
(
:merge_request
,
source_project:
project
)
}
subject
{
described_class
.
approve_mr
(
noteable
,
author
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment