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
27d91a62
Commit
27d91a62
authored
Dec 10, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add latest changes from gitlab-org/gitlab@master
parent
5e11c9b7
Changes
62
Show whitespace changes
Inline
Side-by-side
Showing
62 changed files
with
1070 additions
and
395 deletions
+1070
-395
app/assets/javascripts/pipelines/components/graph/graph_component.vue
...avascripts/pipelines/components/graph/graph_component.vue
+0
-4
app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
...ts/pipelines/components/graph/linked_pipelines_column.vue
+5
-2
app/assets/stylesheets/pages/diff.scss
app/assets/stylesheets/pages/diff.scss
+1
-0
app/controllers/admin/broadcast_messages_controller.rb
app/controllers/admin/broadcast_messages_controller.rb
+1
-0
app/models/broadcast_message.rb
app/models/broadcast_message.rb
+53
-23
app/models/commit.rb
app/models/commit.rb
+4
-0
app/models/commit_user_mention.rb
app/models/commit_user_mention.rb
+5
-0
app/models/concerns/mentionable.rb
app/models/concerns/mentionable.rb
+80
-0
app/models/issue.rb
app/models/issue.rb
+1
-0
app/models/issue_user_mention.rb
app/models/issue_user_mention.rb
+6
-0
app/models/merge_request.rb
app/models/merge_request.rb
+1
-0
app/models/merge_request_user_mention.rb
app/models/merge_request_user_mention.rb
+6
-0
app/models/note.rb
app/models/note.rb
+10
-0
app/models/snippet.rb
app/models/snippet.rb
+3
-0
app/models/snippet_user_mention.rb
app/models/snippet_user_mention.rb
+6
-0
app/models/user_mention.rb
app/models/user_mention.rb
+23
-0
app/services/create_snippet_service.rb
app/services/create_snippet_service.rb
+5
-1
app/services/issuable_base_service.rb
app/services/issuable_base_service.rb
+10
-2
app/services/notes/create_service.rb
app/services/notes/create_service.rb
+5
-1
app/services/notes/update_service.rb
app/services/notes/update_service.rb
+5
-1
app/services/update_snippet_service.rb
app/services/update_snippet_service.rb
+6
-2
app/views/search/_category.html.haml
app/views/search/_category.html.haml
+1
-1
app/views/shared/projects/_project.html.haml
app/views/shared/projects/_project.html.haml
+1
-1
changelogs/unreleased/21800-mentioned-users-models-with-array-type.yml
...released/21800-mentioned-users-models-with-array-type.yml
+5
-0
changelogs/unreleased/nicolasdular-add-broadcast-type.yml
changelogs/unreleased/nicolasdular-add-broadcast-type.yml
+5
-0
changelogs/unreleased/remove-downstream-node-lines.yml
changelogs/unreleased/remove-downstream-node-lines.yml
+5
-0
db/fixtures/development/03_project.rb
db/fixtures/development/03_project.rb
+4
-0
db/migrate/20191129134844_add_broadcast_type_to_broadcast_message.rb
...20191129134844_add_broadcast_type_to_broadcast_message.rb
+19
-0
db/schema.rb
db/schema.rb
+1
-0
doc/ci/yaml/README.md
doc/ci/yaml/README.md
+1
-0
lib/banzai/reference_parser/mentioned_group_parser.rb
lib/banzai/reference_parser/mentioned_group_parser.rb
+1
-1
lib/banzai/reference_parser/mentioned_project_parser.rb
lib/banzai/reference_parser/mentioned_project_parser.rb
+1
-1
lib/gitlab/ci/config/entry/boolean.rb
lib/gitlab/ci/config/entry/boolean.rb
+0
-20
lib/gitlab/ci/config/entry/default.rb
lib/gitlab/ci/config/entry/default.rb
+8
-3
lib/gitlab/ci/config/entry/job.rb
lib/gitlab/ci/config/entry/job.rb
+5
-2
lib/gitlab/config/entry/array_of_strings.rb
lib/gitlab/config/entry/array_of_strings.rb
+18
-0
lib/gitlab/reference_extractor.rb
lib/gitlab/reference_extractor.rb
+1
-1
qa/qa/page/search/results.rb
qa/qa/page/search/results.rb
+17
-4
qa/qa/resource/api_fabricator.rb
qa/qa/resource/api_fabricator.rb
+14
-2
qa/qa/runtime/api/client.rb
qa/qa/runtime/api/client.rb
+17
-0
spec/frontend/environments/environment_item_spec.js
spec/frontend/environments/environment_item_spec.js
+131
-0
spec/frontend/environments/environment_table_spec.js
spec/frontend/environments/environment_table_spec.js
+65
-55
spec/frontend/environments/mock_data.js
spec/frontend/environments/mock_data.js
+106
-0
spec/javascripts/environments/environment_item_spec.js
spec/javascripts/environments/environment_item_spec.js
+0
-230
spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js
...vascripts/pipelines/graph/linked_pipelines_column_spec.js
+4
-0
spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
...ib/banzai/reference_parser/mentioned_group_parser_spec.rb
+1
-1
spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
.../banzai/reference_parser/mentioned_project_parser_spec.rb
+1
-1
spec/lib/gitlab/ci/config/entry/default_spec.rb
spec/lib/gitlab/ci/config/entry/default_spec.rb
+1
-1
spec/lib/gitlab/ci/config/entry/job_spec.rb
spec/lib/gitlab/ci/config/entry/job_spec.rb
+1
-1
spec/lib/gitlab/ci/yaml_processor_spec.rb
spec/lib/gitlab/ci/yaml_processor_spec.rb
+2
-2
spec/lib/gitlab/import_export/all_models.yml
spec/lib/gitlab/import_export/all_models.yml
+4
-0
spec/models/broadcast_message_spec.rb
spec/models/broadcast_message_spec.rb
+87
-32
spec/models/concerns/mentionable_spec.rb
spec/models/concerns/mentionable_spec.rb
+67
-0
spec/models/issue_spec.rb
spec/models/issue_spec.rb
+1
-0
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+1
-0
spec/models/snippet_spec.rb
spec/models/snippet_spec.rb
+1
-0
spec/models/user_mentions/commit_user_mention_spec.rb
spec/models/user_mentions/commit_user_mention_spec.rb
+11
-0
spec/models/user_mentions/issue_user_mention_spec.rb
spec/models/user_mentions/issue_user_mention_spec.rb
+12
-0
spec/models/user_mentions/merge_request_user_mention_spec.rb
spec/models/user_mentions/merge_request_user_mention_spec.rb
+12
-0
spec/models/user_mentions/snippet_user_mention_spec.rb
spec/models/user_mentions/snippet_user_mention_spec.rb
+12
-0
spec/support/shared_examples/mentionable_shared_examples.rb
spec/support/shared_examples/mentionable_shared_examples.rb
+150
-0
spec/support/shared_examples/models/user_mentions_shared_examples.rb
...t/shared_examples/models/user_mentions_shared_examples.rb
+40
-0
No files found.
app/assets/javascripts/pipelines/components/graph/graph_component.vue
View file @
27d91a62
...
@@ -100,9 +100,6 @@ export default {
...
@@ -100,9 +100,6 @@ export default {
hasOnlyOneJob
(
stage
)
{
hasOnlyOneJob
(
stage
)
{
return
stage
.
groups
.
length
===
1
;
return
stage
.
groups
.
length
===
1
;
},
},
hasDownstream
(
index
,
length
)
{
return
index
===
length
-
1
&&
this
.
hasTriggered
;
},
hasUpstream
(
index
)
{
hasUpstream
(
index
)
{
return
index
===
0
&&
this
.
hasTriggeredBy
;
return
index
===
0
&&
this
.
hasTriggeredBy
;
},
},
...
@@ -160,7 +157,6 @@ export default {
...
@@ -160,7 +157,6 @@ export default {
:key=
"stage.name"
:key=
"stage.name"
:class=
"
{
:class=
"
{
'has-upstream prepend-left-64': hasUpstream(index),
'has-upstream prepend-left-64': hasUpstream(index),
'has-downstream': hasDownstream(index, graph.length),
'has-only-one-job': hasOnlyOneJob(stage),
'has-only-one-job': hasOnlyOneJob(stage),
'append-right-46': shouldAddRightMargin(index),
'append-right-46': shouldAddRightMargin(index),
}"
}"
...
...
app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
View file @
27d91a62
<
script
>
<
script
>
import
LinkedPipeline
from
'
./linked_pipeline.vue
'
;
import
LinkedPipeline
from
'
./linked_pipeline.vue
'
;
import
{
__
}
from
'
~/locale
'
;
export
default
{
export
default
{
components
:
{
components
:
{
...
@@ -27,6 +28,9 @@ export default {
...
@@ -27,6 +28,9 @@ export default {
};
};
return
`graph-position-
${
this
.
graphPosition
}
${
positionValues
[
this
.
graphPosition
]}
`
;
return
`graph-position-
${
this
.
graphPosition
}
${
positionValues
[
this
.
graphPosition
]}
`
;
},
},
isUpstream
()
{
return
this
.
columnTitle
===
__
(
'
Upstream
'
);
},
},
},
};
};
</
script
>
</
script
>
...
@@ -34,13 +38,12 @@ export default {
...
@@ -34,13 +38,12 @@ export default {
<
template
>
<
template
>
<div
:class=
"columnClass"
class=
"stage-column linked-pipelines-column"
>
<div
:class=
"columnClass"
class=
"stage-column linked-pipelines-column"
>
<div
class=
"stage-name linked-pipelines-column-title"
>
{{
columnTitle
}}
</div>
<div
class=
"stage-name linked-pipelines-column-title"
>
{{
columnTitle
}}
</div>
<div
class=
"cross-project-triangle"
></div>
<div
v-if=
"isUpstream"
class=
"cross-project-triangle"
></div>
<ul>
<ul>
<linked-pipeline
<linked-pipeline
v-for=
"(pipeline, index) in linkedPipelines"
v-for=
"(pipeline, index) in linkedPipelines"
:key=
"pipeline.id"
:key=
"pipeline.id"
:class=
"
{
:class=
"
{
'flat-connector-before': index === 0
&&
graphPosition === 'right',
active: pipeline.isExpanded,
active: pipeline.isExpanded,
'left-connector': pipeline.isExpanded
&&
graphPosition === 'left',
'left-connector': pipeline.isExpanded
&&
graphPosition === 'left',
}"
}"
...
...
app/assets/stylesheets/pages/diff.scss
View file @
27d91a62
...
@@ -473,6 +473,7 @@ table.code {
...
@@ -473,6 +473,7 @@ table.code {
text-align
:
right
;
text-align
:
right
;
width
:
50px
;
width
:
50px
;
position
:
relative
;
position
:
relative
;
white-space
:
nowrap
;
a
{
a
{
transition
:
none
;
transition
:
none
;
...
...
app/controllers/admin/broadcast_messages_controller.rb
View file @
27d91a62
...
@@ -61,6 +61,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
...
@@ -61,6 +61,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
message
message
starts_at
starts_at
target_path
target_path
broadcast_type
)
)
)
)
end
end
end
end
app/models/broadcast_message.rb
View file @
27d91a62
...
@@ -9,6 +9,7 @@ class BroadcastMessage < ApplicationRecord
...
@@ -9,6 +9,7 @@ class BroadcastMessage < ApplicationRecord
validates
:message
,
presence:
true
validates
:message
,
presence:
true
validates
:starts_at
,
presence:
true
validates
:starts_at
,
presence:
true
validates
:ends_at
,
presence:
true
validates
:ends_at
,
presence:
true
validates
:broadcast_type
,
presence:
true
validates
:color
,
allow_blank:
true
,
color:
true
validates
:color
,
allow_blank:
true
,
color:
true
validates
:font
,
allow_blank:
true
,
color:
true
validates
:font
,
allow_blank:
true
,
color:
true
...
@@ -17,55 +18,82 @@ class BroadcastMessage < ApplicationRecord
...
@@ -17,55 +18,82 @@ class BroadcastMessage < ApplicationRecord
default_value_for
:font
,
'#FFFFFF'
default_value_for
:font
,
'#FFFFFF'
CACHE_KEY
=
'broadcast_message_current_json'
CACHE_KEY
=
'broadcast_message_current_json'
BANNER_CACHE_KEY
=
'broadcast_message_current_banner_json'
NOTIFICATION_CACHE_KEY
=
'broadcast_message_current_notification_json'
after_commit
:flush_redis_cache
after_commit
:flush_redis_cache
def
self
.
current
(
current_path
=
nil
)
enum
broadcast_type:
{
messages
=
cache
.
fetch
(
CACHE_KEY
,
as:
BroadcastMessage
,
expires_in:
cache_expires_in
)
do
banner:
1
,
current_and_future_messages
notification:
2
end
}
return
[]
unless
messages
&
.
present?
class
<<
self
def
current_banner_messages
(
current_path
=
nil
)
now_or_future
=
messages
.
select
(
&
:now_or_future?
)
fetch_messages
BANNER_CACHE_KEY
,
current_path
do
current_and_future_messages
.
banner
end
end
# If there are cached entries but none are to be displayed we'll purge the
def
current_notification_messages
(
current_path
=
nil
)
# cache so we don't keep running this code all the time.
fetch_messages
NOTIFICATION_CACHE_KEY
,
current_path
do
cache
.
expire
(
CACHE_KEY
)
if
now_or_future
.
empty?
current_and_future_messages
.
notification
end
end
now_or_future
.
select
(
&
:now?
).
select
{
|
message
|
message
.
matches_current_path
(
current_path
)
}
def
current
(
current_path
=
nil
)
fetch_messages
CACHE_KEY
,
current_path
do
current_and_future_messages
end
end
end
def
self
.
current_and_future_messages
def
current_and_future_messages
where
(
'ends_at > :now'
,
now:
Time
.
zone
.
now
).
order_id_asc
where
(
'ends_at > :now'
,
now:
Time
.
current
).
order_id_asc
end
end
def
self
.
cache
def
cache
Gitlab
::
JsonCache
.
new
(
cache_key_with_version:
false
)
Gitlab
::
JsonCache
.
new
(
cache_key_with_version:
false
)
end
end
def
self
.
cache_expires_in
def
cache_expires_in
2
.
weeks
2
.
weeks
end
end
private
def
fetch_messages
(
cache_key
,
current_path
)
messages
=
cache
.
fetch
(
cache_key
,
as:
BroadcastMessage
,
expires_in:
cache_expires_in
)
do
yield
end
now_or_future
=
messages
.
select
(
&
:now_or_future?
)
# If there are cached entries but none are to be displayed we'll purge the
# cache so we don't keep running this code all the time.
cache
.
expire
(
cache_key
)
if
now_or_future
.
empty?
now_or_future
.
select
(
&
:now?
).
select
{
|
message
|
message
.
matches_current_path
(
current_path
)
}
end
end
def
active?
def
active?
started?
&&
!
ended?
started?
&&
!
ended?
end
end
def
started?
def
started?
Time
.
zone
.
now
>=
starts_at
Time
.
current
>=
starts_at
end
end
def
ended?
def
ended?
ends_at
<
Time
.
zone
.
now
ends_at
<
Time
.
current
end
end
def
now?
def
now?
(
starts_at
..
ends_at
).
cover?
(
Time
.
zone
.
now
)
(
starts_at
..
ends_at
).
cover?
(
Time
.
current
)
end
end
def
future?
def
future?
starts_at
>
Time
.
zone
.
now
starts_at
>
Time
.
current
end
end
def
now_or_future?
def
now_or_future?
...
@@ -79,7 +107,9 @@ class BroadcastMessage < ApplicationRecord
...
@@ -79,7 +107,9 @@ class BroadcastMessage < ApplicationRecord
end
end
def
flush_redis_cache
def
flush_redis_cache
self
.
class
.
cache
.
expire
(
CACHE_KEY
)
[
CACHE_KEY
,
BANNER_CACHE_KEY
,
NOTIFICATION_CACHE_KEY
].
each
do
|
key
|
self
.
class
.
cache
.
expire
(
key
)
end
end
end
end
end
...
...
app/models/commit.rb
View file @
27d91a62
...
@@ -281,6 +281,10 @@ class Commit
...
@@ -281,6 +281,10 @@ class Commit
project
.
notes
.
for_commit_id
(
self
.
id
)
project
.
notes
.
for_commit_id
(
self
.
id
)
end
end
def
user_mentions
CommitUserMention
.
where
(
commit_id:
self
.
id
)
end
def
discussion_notes
def
discussion_notes
notes
.
non_diff_notes
notes
.
non_diff_notes
end
end
...
...
app/models/commit_user_mention.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
class
CommitUserMention
<
UserMention
belongs_to
:note
end
app/models/concerns/mentionable.rb
View file @
27d91a62
...
@@ -80,6 +80,66 @@ module Mentionable
...
@@ -80,6 +80,66 @@ module Mentionable
all_references
(
current_user
).
users
all_references
(
current_user
).
users
end
end
def
store_mentions!
# if store_mentioned_users_to_db feature flag is not enabled then consider storing operation as succeeded
# because we wrap this method in transaction with with_transaction_returning_status, and we need the status to be
# successful if mentionable.save is successful.
#
# This line will get removed when we remove the feature flag.
return
true
unless
store_mentioned_users_to_db_enabled?
refs
=
all_references
(
self
.
author
)
references
=
{}
references
[
:mentioned_users_ids
]
=
refs
.
mentioned_users
&
.
pluck
(
:id
).
presence
references
[
:mentioned_groups_ids
]
=
refs
.
mentioned_groups
&
.
pluck
(
:id
).
presence
references
[
:mentioned_projects_ids
]
=
refs
.
mentioned_projects
&
.
pluck
(
:id
).
presence
# One retry should be enough as next time `model_user_mention` should return the existing mention record, that
# threw the `ActiveRecord::RecordNotUnique` exception in first place.
self
.
class
.
safe_ensure_unique
(
retries:
1
)
do
user_mention
=
model_user_mention
user_mention
.
mentioned_users_ids
=
references
[
:mentioned_users_ids
]
user_mention
.
mentioned_groups_ids
=
references
[
:mentioned_groups_ids
]
user_mention
.
mentioned_projects_ids
=
references
[
:mentioned_projects_ids
]
if
user_mention
.
has_mentions?
user_mention
.
save!
elsif
user_mention
.
persisted?
user_mention
.
destroy!
end
true
end
end
def
referenced_users
User
.
where
(
id:
user_mentions
.
select
(
"unnest(mentioned_users_ids)"
))
end
def
referenced_projects
(
current_user
=
nil
)
Project
.
where
(
id:
user_mentions
.
select
(
"unnest(mentioned_projects_ids)"
)).
public_or_visible_to_user
(
current_user
)
end
def
referenced_project_users
(
current_user
=
nil
)
User
.
joins
(
:project_members
).
where
(
members:
{
source_id:
referenced_projects
(
current_user
)
}).
distinct
end
def
referenced_groups
(
current_user
=
nil
)
# TODO: IMPORTANT: Revisit before using it.
# Check DB data for max mentioned groups per mentionable:
#
# select issue_id, count(mentions_count.men_gr_id) gr_count from
# (select DISTINCT unnest(mentioned_groups_ids) as men_gr_id, issue_id
# from issue_user_mentions group by issue_id, mentioned_groups_ids) as mentions_count
# group by mentions_count.issue_id order by gr_count desc limit 10
Group
.
where
(
id:
user_mentions
.
select
(
"unnest(mentioned_groups_ids)"
)).
public_or_visible_to_user
(
current_user
)
end
def
referenced_group_users
(
current_user
=
nil
)
User
.
joins
(
:group_members
).
where
(
members:
{
source_id:
referenced_groups
}).
distinct
end
def
directly_addressed_users
(
current_user
=
nil
)
def
directly_addressed_users
(
current_user
=
nil
)
all_references
(
current_user
).
directly_addressed_users
all_references
(
current_user
).
directly_addressed_users
end
end
...
@@ -171,6 +231,26 @@ module Mentionable
...
@@ -171,6 +231,26 @@ module Mentionable
def
mentionable_params
def
mentionable_params
{}
{}
end
end
# User mention that is parsed from model description rather then its related notes.
# Models that have a descriprion attribute like Issue, MergeRequest, Epic, Snippet may have such a user mention.
# Other mentionable models like Commit, DesignManagement::Design, will never have such record as those do not have
# a description attribute.
#
# Using this method followed by a call to *save* may result in *ActiveRecord::RecordNotUnique* exception
# in a multithreaded environment. Make sure to use it within a *safe_ensure_unique* block.
def
model_user_mention
user_mentions
.
where
(
note_id:
nil
).
first_or_initialize
end
# We need this method to be checking that store_mentioned_users_to_db feature flag is enabled at the group level
# and not the project level as epics are defined at group level and we want to have epics store user mentions as well
# for the test period.
# During the test period the flag should be enabled at the group level.
def
store_mentioned_users_to_db_enabled?
return
Feature
.
enabled?
(
:store_mentioned_users_to_db
,
self
.
project
&
.
group
)
if
self
.
respond_to?
(
:project
)
return
Feature
.
enabled?
(
:store_mentioned_users_to_db
,
self
.
group
)
if
self
.
respond_to?
(
:group
)
end
end
end
Mentionable
.
prepend_if_ee
(
'EE::Mentionable'
)
Mentionable
.
prepend_if_ee
(
'EE::Mentionable'
)
app/models/issue.rb
View file @
27d91a62
...
@@ -42,6 +42,7 @@ class Issue < ApplicationRecord
...
@@ -42,6 +42,7 @@ class Issue < ApplicationRecord
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_many
:zoom_meetings
has_many
:zoom_meetings
has_many
:user_mentions
,
class_name:
"IssueUserMention"
has_one
:sentry_issue
has_one
:sentry_issue
validates
:project
,
presence:
true
validates
:project
,
presence:
true
...
...
app/models/issue_user_mention.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
class
IssueUserMention
<
UserMention
belongs_to
:issue
belongs_to
:note
end
app/models/merge_request.rb
View file @
27d91a62
...
@@ -71,6 +71,7 @@ class MergeRequest < ApplicationRecord
...
@@ -71,6 +71,7 @@ class MergeRequest < ApplicationRecord
has_many
:merge_request_assignees
has_many
:merge_request_assignees
has_many
:assignees
,
class_name:
"User"
,
through: :merge_request_assignees
has_many
:assignees
,
class_name:
"User"
,
through: :merge_request_assignees
has_many
:user_mentions
,
class_name:
"MergeRequestUserMention"
has_many
:deployment_merge_requests
has_many
:deployment_merge_requests
...
...
app/models/merge_request_user_mention.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
class
MergeRequestUserMention
<
UserMention
belongs_to
:merge_request
belongs_to
:note
end
app/models/note.rb
View file @
27d91a62
...
@@ -499,8 +499,18 @@ class Note < ApplicationRecord
...
@@ -499,8 +499,18 @@ class Note < ApplicationRecord
project
project
end
end
def
user_mentions
noteable
.
user_mentions
.
where
(
note:
self
)
end
private
private
# Using this method followed by a call to `save` may result in ActiveRecord::RecordNotUnique exception
# in a multithreaded environment. Make sure to use it within a `safe_ensure_unique` block.
def
model_user_mention
user_mentions
.
first_or_initialize
end
def
system_note_viewable_by?
(
user
)
def
system_note_viewable_by?
(
user
)
return
true
unless
system_note_metadata
return
true
unless
system_note_metadata
...
...
app/models/snippet.rb
View file @
27d91a62
...
@@ -37,6 +37,7 @@ class Snippet < ApplicationRecord
...
@@ -37,6 +37,7 @@ class Snippet < ApplicationRecord
belongs_to
:project
belongs_to
:project
has_many
:notes
,
as: :noteable
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
has_many
:notes
,
as: :noteable
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
has_many
:user_mentions
,
class_name:
"SnippetUserMention"
delegate
:name
,
:email
,
to: :author
,
prefix:
true
,
allow_nil:
true
delegate
:name
,
:email
,
to: :author
,
prefix:
true
,
allow_nil:
true
...
@@ -69,6 +70,8 @@ class Snippet < ApplicationRecord
...
@@ -69,6 +70,8 @@ class Snippet < ApplicationRecord
scope
:inc_author
,
->
{
includes
(
:author
)
}
scope
:inc_author
,
->
{
includes
(
:author
)
}
scope
:inc_relations_for_view
,
->
{
includes
(
author: :status
)
}
scope
:inc_relations_for_view
,
->
{
includes
(
author: :status
)
}
attr_mentionable
:description
participant
:author
participant
:author
participant
:notes_with_associations
participant
:notes_with_associations
...
...
app/models/snippet_user_mention.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
class
SnippetUserMention
<
UserMention
belongs_to
:snippet
belongs_to
:note
end
app/models/user_mention.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
class
UserMention
<
ApplicationRecord
self
.
abstract_class
=
true
def
has_mentions?
mentioned_users_ids
.
present?
||
mentioned_groups_ids
.
present?
||
mentioned_projects_ids
.
present?
end
private
def
mentioned_users
User
.
where
(
id:
mentioned_users_ids
)
end
def
mentioned_groups
Group
.
where
(
id:
mentioned_groups_ids
)
end
def
mentioned_projects
Project
.
where
(
id:
mentioned_projects_ids
)
end
end
app/services/create_snippet_service.rb
View file @
27d91a62
...
@@ -21,7 +21,11 @@ class CreateSnippetService < BaseService
...
@@ -21,7 +21,11 @@ class CreateSnippetService < BaseService
spam_check
(
snippet
,
current_user
)
spam_check
(
snippet
,
current_user
)
if
snippet
.
save
snippet_saved
=
snippet
.
with_transaction_returning_status
do
snippet
.
save
&&
snippet
.
store_mentions!
end
if
snippet_saved
UserAgentDetailService
.
new
(
snippet
,
@request
).
create
UserAgentDetailService
.
new
(
snippet
,
@request
).
create
Gitlab
::
UsageDataCounters
::
SnippetCounter
.
count
(
:create
)
Gitlab
::
UsageDataCounters
::
SnippetCounter
.
count
(
:create
)
end
end
...
...
app/services/issuable_base_service.rb
View file @
27d91a62
...
@@ -163,7 +163,11 @@ class IssuableBaseService < BaseService
...
@@ -163,7 +163,11 @@ class IssuableBaseService < BaseService
before_create
(
issuable
)
before_create
(
issuable
)
if
issuable
.
save
issuable_saved
=
issuable
.
with_transaction_returning_status
do
issuable
.
save
&&
issuable
.
store_mentions!
end
if
issuable_saved
Issuable
::
CommonSystemNotesService
.
new
(
project
,
current_user
).
execute
(
issuable
,
is_update:
false
)
Issuable
::
CommonSystemNotesService
.
new
(
project
,
current_user
).
execute
(
issuable
,
is_update:
false
)
after_create
(
issuable
)
after_create
(
issuable
)
...
@@ -224,7 +228,11 @@ class IssuableBaseService < BaseService
...
@@ -224,7 +228,11 @@ class IssuableBaseService < BaseService
update_project_counters
=
issuable
.
project
&&
update_project_counter_caches?
(
issuable
)
update_project_counters
=
issuable
.
project
&&
update_project_counter_caches?
(
issuable
)
ensure_milestone_available
(
issuable
)
ensure_milestone_available
(
issuable
)
if
issuable
.
with_transaction_returning_status
{
issuable
.
save
(
touch:
should_touch
)
}
issuable_saved
=
issuable
.
with_transaction_returning_status
do
issuable
.
save
(
touch:
should_touch
)
&&
issuable
.
store_mentions!
end
if
issuable_saved
Issuable
::
CommonSystemNotesService
.
new
(
project
,
current_user
).
execute
(
issuable
,
old_labels:
old_associations
[
:labels
])
Issuable
::
CommonSystemNotesService
.
new
(
project
,
current_user
).
execute
(
issuable
,
old_labels:
old_associations
[
:labels
])
handle_changes
(
issuable
,
old_associations:
old_associations
)
handle_changes
(
issuable
,
old_associations:
old_associations
)
...
...
app/services/notes/create_service.rb
View file @
27d91a62
...
@@ -33,7 +33,11 @@ module Notes
...
@@ -33,7 +33,11 @@ module Notes
NewNoteWorker
.
perform_async
(
note
.
id
)
NewNoteWorker
.
perform_async
(
note
.
id
)
end
end
if
!
only_commands
&&
note
.
save
note_saved
=
note
.
with_transaction_returning_status
do
!
only_commands
&&
note
.
save
&&
note
.
store_mentions!
end
if
note_saved
if
note
.
part_of_discussion?
&&
note
.
discussion
.
can_convert_to_discussion?
if
note
.
part_of_discussion?
&&
note
.
discussion
.
can_convert_to_discussion?
note
.
discussion
.
convert_to_discussion!
(
save:
true
)
note
.
discussion
.
convert_to_discussion!
(
save:
true
)
end
end
...
...
app/services/notes/update_service.rb
View file @
27d91a62
...
@@ -7,7 +7,11 @@ module Notes
...
@@ -7,7 +7,11 @@ module Notes
old_mentioned_users
=
note
.
mentioned_users
(
current_user
).
to_a
old_mentioned_users
=
note
.
mentioned_users
(
current_user
).
to_a
note
.
update
(
params
.
merge
(
updated_by:
current_user
))
note
.
assign_attributes
(
params
.
merge
(
updated_by:
current_user
))
note
.
with_transaction_returning_status
do
note
.
save
&&
note
.
store_mentions!
end
only_commands
=
false
only_commands
=
false
...
...
app/services/update_snippet_service.rb
View file @
27d91a62
...
@@ -25,8 +25,12 @@ class UpdateSnippetService < BaseService
...
@@ -25,8 +25,12 @@ class UpdateSnippetService < BaseService
snippet
.
assign_attributes
(
params
)
snippet
.
assign_attributes
(
params
)
spam_check
(
snippet
,
current_user
)
spam_check
(
snippet
,
current_user
)
snippet
.
save
.
tap
do
|
succeeded
|
snippet_saved
=
snippet
.
with_transaction_returning_status
do
Gitlab
::
UsageDataCounters
::
SnippetCounter
.
count
(
:update
)
if
succeeded
snippet
.
save
&&
snippet
.
store_mentions!
end
if
snippet_saved
Gitlab
::
UsageDataCounters
::
SnippetCounter
.
count
(
:update
)
end
end
end
end
end
end
app/views/search/_category.html.haml
View file @
27d91a62
...
@@ -27,7 +27,7 @@
...
@@ -27,7 +27,7 @@
=
search_filter_link
'snippet_blobs'
,
_
(
"Snippet Contents"
),
search:
{
snippets:
true
,
group_id:
nil
,
project_id:
nil
}
=
search_filter_link
'snippet_blobs'
,
_
(
"Snippet Contents"
),
search:
{
snippets:
true
,
group_id:
nil
,
project_id:
nil
}
=
search_filter_link
'snippet_titles'
,
_
(
"Titles and Filenames"
),
search:
{
snippets:
true
,
group_id:
nil
,
project_id:
nil
}
=
search_filter_link
'snippet_titles'
,
_
(
"Titles and Filenames"
),
search:
{
snippets:
true
,
group_id:
nil
,
project_id:
nil
}
-
else
-
else
=
search_filter_link
'projects'
,
_
(
"Projects"
)
=
search_filter_link
'projects'
,
_
(
"Projects"
)
,
data:
{
qa_selector:
'projects_tab'
}
=
search_filter_link
'issues'
,
_
(
"Issues"
)
=
search_filter_link
'issues'
,
_
(
"Issues"
)
=
search_filter_link
'merge_requests'
,
_
(
"Merge requests"
)
=
search_filter_link
'merge_requests'
,
_
(
"Merge requests"
)
=
search_filter_link
'milestones'
,
_
(
"Milestones"
)
=
search_filter_link
'milestones'
,
_
(
"Milestones"
)
...
...
app/views/shared/projects/_project.html.haml
View file @
27d91a62
...
@@ -26,7 +26,7 @@
...
@@ -26,7 +26,7 @@
=
image_tag
avatar_icon_for_user
(
project
.
creator
,
48
),
class:
"avatar s48"
,
alt
:''
=
image_tag
avatar_icon_for_user
(
project
.
creator
,
48
),
class:
"avatar s48"
,
alt
:''
-
else
-
else
=
project_icon
(
project
,
alt:
''
,
class:
'avatar project-avatar s48'
,
width:
48
,
height:
48
)
=
project_icon
(
project
,
alt:
''
,
class:
'avatar project-avatar s48'
,
width:
48
,
height:
48
)
.project-details.d-sm-flex.flex-sm-fill.align-items-center
.project-details.d-sm-flex.flex-sm-fill.align-items-center
{
data:
{
qa_selector:
'project'
,
qa_project_name:
project
.
name
}
}
.flex-wrapper
.flex-wrapper
.d-flex.align-items-center.flex-wrap.project-title
.d-flex.align-items-center.flex-wrap.project-title
%h2
.d-flex.prepend-top-8
%h2
.d-flex.prepend-top-8
...
...
changelogs/unreleased/21800-mentioned-users-models-with-array-type.yml
0 → 100644
View file @
27d91a62
---
title
:
Store users, groups, projects mentioned in Markdown to DB tables
merge_request
:
19088
author
:
type
:
added
changelogs/unreleased/nicolasdular-add-broadcast-type.yml
0 → 100644
View file @
27d91a62
---
title
:
Add type to broadcast messages
merge_request
:
21038
author
:
type
:
added
changelogs/unreleased/remove-downstream-node-lines.yml
0 → 100644
View file @
27d91a62
---
title
:
Remove downstream pipeline connecting lines
merge_request
:
21196
author
:
type
:
removed
db/fixtures/development/03_project.rb
View file @
27d91a62
...
@@ -141,6 +141,10 @@ class Gitlab::Seeder::Projects
...
@@ -141,6 +141,10 @@ class Gitlab::Seeder::Projects
# the `after_commit` queue to ensure the job is run now.
# the `after_commit` queue to ensure the job is run now.
project
.
send
(
:_run_after_commit_queue
)
project
.
send
(
:_run_after_commit_queue
)
project
.
import_state
.
send
(
:_run_after_commit_queue
)
project
.
import_state
.
send
(
:_run_after_commit_queue
)
# Expire repository cache after import to ensure
# valid_repo? call below returns a correct answer
project
.
repository
.
expire_all_method_caches
end
end
if
project
.
valid?
&&
project
.
valid_repo?
if
project
.
valid?
&&
project
.
valid_repo?
...
...
db/migrate/20191129134844_add_broadcast_type_to_broadcast_message.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
class
AddBroadcastTypeToBroadcastMessage
<
ActiveRecord
::
Migration
[
5.2
]
include
Gitlab
::
Database
::
MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME
=
false
BROADCAST_MESSAGE_BANNER_TYPE
=
1
disable_ddl_transaction!
def
up
add_column_with_default
(
:broadcast_messages
,
:broadcast_type
,
:smallint
,
default:
BROADCAST_MESSAGE_BANNER_TYPE
)
end
def
down
remove_column
(
:broadcast_messages
,
:broadcast_type
)
end
end
db/schema.rb
View file @
27d91a62
...
@@ -575,6 +575,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
...
@@ -575,6 +575,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
t
.
text
"message_html"
,
null:
false
t
.
text
"message_html"
,
null:
false
t
.
integer
"cached_markdown_version"
t
.
integer
"cached_markdown_version"
t
.
string
"target_path"
,
limit:
255
t
.
string
"target_path"
,
limit:
255
t
.
integer
"broadcast_type"
,
limit:
2
,
default:
1
,
null:
false
t
.
index
[
"starts_at"
,
"ends_at"
,
"id"
],
name:
"index_broadcast_messages_on_starts_at_and_ends_at_and_id"
t
.
index
[
"starts_at"
,
"ends_at"
,
"id"
],
name:
"index_broadcast_messages_on_starts_at_and_ends_at_and_id"
end
end
...
...
doc/ci/yaml/README.md
View file @
27d91a62
...
@@ -135,6 +135,7 @@ The following job parameters can be defined inside a `default:` block:
...
@@ -135,6 +135,7 @@ The following job parameters can be defined inside a `default:` block:
-
[
`services`
](
#services
)
-
[
`services`
](
#services
)
-
[
`before_script`
](
#before_script-and-after_script
)
-
[
`before_script`
](
#before_script-and-after_script
)
-
[
`after_script`
](
#before_script-and-after_script
)
-
[
`after_script`
](
#before_script-and-after_script
)
-
[
`tags`
](
#tags
)
-
[
`cache`
](
#cache
)
-
[
`cache`
](
#cache
)
-
[
`retry`
](
#retry
)
-
[
`retry`
](
#retry
)
-
[
`timeout`
](
#timeout
)
-
[
`timeout`
](
#timeout
)
...
...
lib/banzai/reference_parser/mentioned_
users_by_
group_parser.rb
→
lib/banzai/reference_parser/mentioned_group_parser.rb
View file @
27d91a62
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
module
Banzai
module
Banzai
module
ReferenceParser
module
ReferenceParser
class
Mentioned
UsersBy
GroupParser
<
BaseParser
class
MentionedGroupParser
<
BaseParser
GROUP_ATTR
=
'data-group'
GROUP_ATTR
=
'data-group'
self
.
reference_type
=
:user
self
.
reference_type
=
:user
...
...
lib/banzai/reference_parser/mentioned_
users_by_
project_parser.rb
→
lib/banzai/reference_parser/mentioned_project_parser.rb
View file @
27d91a62
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
module
Banzai
module
Banzai
module
ReferenceParser
module
ReferenceParser
class
Mentioned
UsersBy
ProjectParser
<
ProjectParser
class
MentionedProjectParser
<
ProjectParser
PROJECT_ATTR
=
'data-project'
PROJECT_ATTR
=
'data-project'
self
.
reference_type
=
:user
self
.
reference_type
=
:user
...
...
lib/gitlab/ci/config/entry/boolean.rb
deleted
100644 → 0
View file @
5e11c9b7
# frozen_string_literal: true
module
Gitlab
module
Ci
class
Config
module
Entry
##
# Entry that represents the interrutible value.
#
class
Boolean
<
::
Gitlab
::
Config
::
Entry
::
Node
include
::
Gitlab
::
Config
::
Entry
::
Validatable
validations
do
validates
:config
,
boolean:
true
end
end
end
end
end
end
lib/gitlab/ci/config/entry/default.rb
View file @
27d91a62
...
@@ -15,7 +15,7 @@ module Gitlab
...
@@ -15,7 +15,7 @@ module Gitlab
ALLOWED_KEYS
=
%i[before_script image services
ALLOWED_KEYS
=
%i[before_script image services
after_script cache interruptible
after_script cache interruptible
timeout retry]
.
freeze
timeout retry
tags
]
.
freeze
validations
do
validations
do
validates
:config
,
allowed_keys:
ALLOWED_KEYS
validates
:config
,
allowed_keys:
ALLOWED_KEYS
...
@@ -41,7 +41,7 @@ module Gitlab
...
@@ -41,7 +41,7 @@ module Gitlab
description:
'Configure caching between build jobs.'
,
description:
'Configure caching between build jobs.'
,
inherit:
true
inherit:
true
entry
:interruptible
,
Entry
::
Boolean
,
entry
:interruptible
,
::
Gitlab
::
Config
::
Entry
::
Boolean
,
description:
'Set jobs interruptible default value.'
,
description:
'Set jobs interruptible default value.'
,
inherit:
false
inherit:
false
...
@@ -53,7 +53,12 @@ module Gitlab
...
@@ -53,7 +53,12 @@ module Gitlab
description:
'Set retry default value.'
,
description:
'Set retry default value.'
,
inherit:
false
inherit:
false
helpers
:before_script
,
:image
,
:services
,
:after_script
,
:cache
,
:interruptible
,
:timeout
,
:retry
entry
:tags
,
::
Gitlab
::
Config
::
Entry
::
ArrayOfStrings
,
description:
'Set the default tags.'
,
inherit:
false
helpers
:before_script
,
:image
,
:services
,
:after_script
,
:cache
,
:interruptible
,
:timeout
,
:retry
,
:tags
private
private
...
...
lib/gitlab/ci/config/entry/job.rb
View file @
27d91a62
...
@@ -36,7 +36,6 @@ module Gitlab
...
@@ -36,7 +36,6 @@ module Gitlab
if: :has_rules?
if: :has_rules?
with_options
allow_nil:
true
do
with_options
allow_nil:
true
do
validates
:tags
,
array_of_strings:
true
validates
:allow_failure
,
boolean:
true
validates
:allow_failure
,
boolean:
true
validates
:parallel
,
numericality:
{
only_integer:
true
,
validates
:parallel
,
numericality:
{
only_integer:
true
,
greater_than_or_equal_to:
2
,
greater_than_or_equal_to:
2
,
...
@@ -97,7 +96,7 @@ module Gitlab
...
@@ -97,7 +96,7 @@ module Gitlab
description:
'Services that will be used to execute this job.'
,
description:
'Services that will be used to execute this job.'
,
inherit:
true
inherit:
true
entry
:interruptible
,
Entry
::
Boolean
,
entry
:interruptible
,
::
Gitlab
::
Config
::
Entry
::
Boolean
,
description:
'Set jobs interruptible value.'
,
description:
'Set jobs interruptible value.'
,
inherit:
true
inherit:
true
...
@@ -109,6 +108,10 @@ module Gitlab
...
@@ -109,6 +108,10 @@ module Gitlab
description:
'Retry configuration for this job.'
,
description:
'Retry configuration for this job.'
,
inherit:
true
inherit:
true
entry
:tags
,
::
Gitlab
::
Config
::
Entry
::
ArrayOfStrings
,
description:
'Set the tags.'
,
inherit:
true
entry
:only
,
Entry
::
Policy
,
entry
:only
,
Entry
::
Policy
,
description:
'Refs policy this job will be executed for.'
,
description:
'Refs policy this job will be executed for.'
,
default:
Entry
::
Policy
::
DEFAULT_ONLY
,
default:
Entry
::
Policy
::
DEFAULT_ONLY
,
...
...
lib/gitlab/config/entry/array_of_strings.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
module
Gitlab
module
Config
module
Entry
##
# Entry that represents a array of strings value.
#
class
ArrayOfStrings
<
Node
include
Validatable
validations
do
validates
:config
,
array_of_strings:
true
end
end
end
end
end
lib/gitlab/reference_extractor.rb
View file @
27d91a62
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
module
Gitlab
module
Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
# Extract possible GFM references from an arbitrary String for further processing.
class
ReferenceExtractor
<
Banzai
::
ReferenceExtractor
class
ReferenceExtractor
<
Banzai
::
ReferenceExtractor
REFERABLES
=
%i(user issue label milestone
REFERABLES
=
%i(user issue label milestone
mentioned_user mentioned_group mentioned_project
merge_request snippet commit commit_range directly_addressed_user epic)
.
freeze
merge_request snippet commit commit_range directly_addressed_user epic)
.
freeze
attr_accessor
:project
,
:current_user
,
:author
attr_accessor
:project
,
:current_user
,
:author
...
...
qa/qa/page/search/results.rb
View file @
27d91a62
...
@@ -5,6 +5,7 @@ module QA::Page
...
@@ -5,6 +5,7 @@ module QA::Page
class
Results
<
QA
::
Page
::
Base
class
Results
<
QA
::
Page
::
Base
view
'app/views/search/_category.html.haml'
do
view
'app/views/search/_category.html.haml'
do
element
:code_tab
element
:code_tab
element
:projects_tab
end
end
view
'app/views/search/results/_blob_data.html.haml'
do
view
'app/views/search/results/_blob_data.html.haml'
do
...
@@ -13,20 +14,32 @@ module QA::Page
...
@@ -13,20 +14,32 @@ module QA::Page
element
:file_text_content
element
:file_text_content
end
end
view
'app/views/shared/projects/_project.html.haml'
do
element
:project
end
def
switch_to_code
def
switch_to_code
click_element
(
:code_tab
)
click_element
(
:code_tab
)
end
end
def
switch_to_projects
click_element
(
:projects_tab
)
end
def
has_file_in_project?
(
file_name
,
project_name
)
def
has_file_in_project?
(
file_name
,
project_name
)
has_element?
:result_item_content
,
text:
"
#{
project_name
}
:
#{
file_name
}
"
has_element?
(
:result_item_content
,
text:
"
#{
project_name
}
:
#{
file_name
}
"
)
end
end
def
has_file_with_content?
(
file_name
,
file_text
)
def
has_file_with_content?
(
file_name
,
file_text
)
within_element_by_index
:result_item_content
,
0
do
within_element_by_index
(
:result_item_content
,
0
)
do
false
unless
has_element?
:file_title_content
,
text:
file_name
false
unless
has_element?
(
:file_title_content
,
text:
file_name
)
has_element?
:file_text_content
,
text:
file_text
has_element?
(
:file_text_content
,
text:
file_text
)
end
end
end
def
has_project?
(
project_name
)
has_element?
(
:project
,
project_name:
project_name
)
end
end
end
end
end
end
...
...
qa/qa/resource/api_fabricator.rb
View file @
27d91a62
...
@@ -19,8 +19,8 @@ module QA
...
@@ -19,8 +19,8 @@ module QA
def
api_support?
def
api_support?
respond_to?
(
:api_get_path
)
&&
respond_to?
(
:api_get_path
)
&&
respond_to?
(
:api_post_path
)
&&
(
respond_to?
(
:api_post_path
)
&&
respond_to?
(
:api_post_body
))
||
respond_to?
(
:api_post_body
)
(
respond_to?
(
:api_put_path
)
&&
respond_to?
(
:api_put_body
)
)
end
end
def
fabricate_via_api!
def
fabricate_via_api!
...
@@ -84,6 +84,18 @@ module QA
...
@@ -84,6 +84,18 @@ module QA
process_api_response
(
parse_body
(
response
))
process_api_response
(
parse_body
(
response
))
end
end
def
api_put
response
=
put
(
Runtime
::
API
::
Request
.
new
(
api_client
,
api_put_path
).
url
,
api_put_body
)
unless
response
.
code
==
HTTP_STATUS_OK
raise
ResourceFabricationFailedError
,
"Updating
#{
self
.
class
.
name
}
using the API failed (
#{
response
.
code
}
) with `
#{
response
}
`."
end
process_api_response
(
parse_body
(
response
))
end
def
api_delete
def
api_delete
url
=
Runtime
::
API
::
Request
.
new
(
api_client
,
api_delete_path
).
url
url
=
Runtime
::
API
::
Request
.
new
(
api_client
,
api_delete_path
).
url
response
=
delete
(
url
)
response
=
delete
(
url
)
...
...
qa/qa/runtime/api/client.rb
View file @
27d91a62
...
@@ -25,6 +25,23 @@ module QA
...
@@ -25,6 +25,23 @@ module QA
end
end
end
end
def
self
.
as_admin
if
Runtime
::
Env
.
admin_personal_access_token
Runtime
::
API
::
Client
.
new
(
:gitlab
,
personal_access_token:
Runtime
::
Env
.
admin_personal_access_token
)
else
user
=
Resource
::
User
.
fabricate_via_api!
do
|
user
|
user
.
username
=
Runtime
::
User
.
admin_username
user
.
password
=
Runtime
::
User
.
admin_password
end
unless
user
.
admin?
raise
AuthorizationError
,
"User '
#{
user
.
username
}
' is not an administrator."
end
Runtime
::
API
::
Client
.
new
(
:gitlab
,
user:
user
)
end
end
private
private
def
enable_ip_limits
def
enable_ip_limits
...
...
spec/frontend/environments/environment_item_spec.js
0 → 100644
View file @
27d91a62
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
format
}
from
'
timeago.js
'
;
import
EnvironmentItem
from
'
~/environments/components/environment_item.vue
'
;
import
{
environment
,
folder
,
tableData
}
from
'
./mock_data
'
;
describe
(
'
Environment item
'
,
()
=>
{
let
wrapper
;
const
factory
=
(
options
=
{})
=>
{
// This destroys any wrappers created before a nested call to factory reassigns it
if
(
wrapper
&&
wrapper
.
destroy
)
{
wrapper
.
destroy
();
}
wrapper
=
mount
(
EnvironmentItem
,
{
...
options
,
});
};
beforeEach
(()
=>
{
factory
({
propsData
:
{
model
:
environment
,
canReadEnvironment
:
true
,
tableData
,
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
when item is not folder
'
,
()
=>
{
it
(
'
should render environment name
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.environment-name
'
).
text
()).
toContain
(
environment
.
name
);
});
describe
(
'
With deployment
'
,
()
=>
{
it
(
'
should render deployment internal id
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.deployment-column span
'
).
text
()).
toContain
(
environment
.
last_deployment
.
iid
,
);
expect
(
wrapper
.
find
(
'
.deployment-column span
'
).
text
()).
toContain
(
'
#
'
);
});
it
(
'
should render last deployment date
'
,
()
=>
{
const
formatedDate
=
format
(
environment
.
last_deployment
.
deployed_at
);
expect
(
wrapper
.
find
(
'
.environment-created-date-timeago
'
).
text
()).
toContain
(
formatedDate
);
});
describe
(
'
With user information
'
,
()
=>
{
it
(
'
should render user avatar with link to profile
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-deploy-user-container
'
).
attributes
(
'
href
'
)).
toEqual
(
environment
.
last_deployment
.
user
.
web_url
,
);
});
});
describe
(
'
With build url
'
,
()
=>
{
it
(
'
should link to build url provided
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.build-link
'
).
attributes
(
'
href
'
)).
toEqual
(
environment
.
last_deployment
.
deployable
.
build_path
,
);
});
it
(
'
should render deployable name and id
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.build-link
'
).
attributes
(
'
href
'
)).
toEqual
(
environment
.
last_deployment
.
deployable
.
build_path
,
);
});
});
describe
(
'
With commit information
'
,
()
=>
{
it
(
'
should render commit component
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-commit-component
'
)).
toBeDefined
();
});
});
});
describe
(
'
With manual actions
'
,
()
=>
{
it
(
'
should render actions component
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-manual-actions-container
'
)).
toBeDefined
();
});
});
describe
(
'
With external URL
'
,
()
=>
{
it
(
'
should render external url component
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-external-url-container
'
)).
toBeDefined
();
});
});
describe
(
'
With stop action
'
,
()
=>
{
it
(
'
should render stop action component
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-stop-component-container
'
)).
toBeDefined
();
});
});
describe
(
'
With retry action
'
,
()
=>
{
it
(
'
should render rollback component
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-rollback-component-container
'
)).
toBeDefined
();
});
});
});
describe
(
'
When item is folder
'
,
()
=>
{
beforeEach
(()
=>
{
factory
({
propsData
:
{
model
:
folder
,
canReadEnvironment
:
true
,
tableData
,
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
should render folder icon and name
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.folder-name
'
).
text
()).
toContain
(
folder
.
name
);
expect
(
wrapper
.
find
(
'
.folder-icon
'
)).
toBeDefined
();
});
it
(
'
should render the number of children in a badge
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.folder-name .badge
'
).
text
()).
toContain
(
folder
.
size
);
});
});
});
spec/
javascripts
/environments/environment_table_spec.js
→
spec/
frontend
/environments/environment_table_spec.js
View file @
27d91a62
import
Vue
from
'
vue
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
EnvironmentTable
from
'
~/environments/components/environments_table.vue
'
;
import
environmentTableComp
from
'
~/environments/components/environments_table.vue
'
;
import
{
folder
}
from
'
./mock_data
'
;
describe
(
'
Environment table
'
,
()
=>
{
const
eeOnlyProps
=
{
let
Component
;
let
vm
;
const
eeOnlyProps
=
{
canaryDeploymentFeatureId
:
'
canary_deployment
'
,
canaryDeploymentFeatureId
:
'
canary_deployment
'
,
showCanaryDeploymentCallout
:
true
,
showCanaryDeploymentCallout
:
true
,
userCalloutsPath
:
'
/callouts
'
,
userCalloutsPath
:
'
/callouts
'
,
lockPromotionSvgPath
:
'
/assets/illustrations/lock-promotion.svg
'
,
lockPromotionSvgPath
:
'
/assets/illustrations/lock-promotion.svg
'
,
helpCanaryDeploymentsPath
:
'
help/canary-deployments
'
,
helpCanaryDeploymentsPath
:
'
help/canary-deployments
'
,
};
};
beforeEach
(()
=>
{
describe
(
'
Environment table
'
,
()
=>
{
Component
=
Vue
.
extend
(
environmentTableComp
);
let
wrapper
;
});
afterEach
(()
=>
{
const
factory
=
(
options
=
{})
=>
{
vm
.
$destroy
();
// This destroys any wrappers created before a nested call to factory reassigns it
if
(
wrapper
&&
wrapper
.
destroy
)
{
wrapper
.
destroy
();
}
wrapper
=
mount
(
EnvironmentTable
,
{
...
options
,
});
});
it
(
'
Should render a table
'
,
()
=>
{
const
mockItem
=
{
name
:
'
review
'
,
size
:
3
,
isFolder
:
true
,
latest
:
{
environment_path
:
'
url
'
,
},
};
};
vm
=
mountComponent
(
Component
,
{
beforeEach
(()
=>
{
environments
:
[
mockItem
],
factory
({
propsData
:
{
environments
:
[
folder
],
canReadEnvironment
:
true
,
canReadEnvironment
:
true
,
...
eeOnlyProps
,
...
eeOnlyProps
,
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
});
expect
(
vm
.
$el
.
getAttribute
(
'
class
'
)).
toContain
(
'
ci-table
'
);
it
(
'
Should render a table
'
,
()
=>
{
expect
(
wrapper
.
classes
()).
toContain
(
'
ci-table
'
);
});
});
describe
(
'
sortEnvironments
'
,
()
=>
{
describe
(
'
sortEnvironments
'
,
()
=>
{
...
@@ -73,15 +73,17 @@ describe('Environment table', () => {
...
@@ -73,15 +73,17 @@ describe('Environment table', () => {
},
},
];
];
vm
=
mountComponent
(
Component
,
{
factory
({
propsData
:
{
environments
:
mockItems
,
environments
:
mockItems
,
canReadEnvironment
:
true
,
canReadEnvironment
:
true
,
...
eeOnlyProps
,
...
eeOnlyProps
,
},
});
});
const
[
old
,
newer
,
older
,
noDeploy
]
=
mockItems
;
const
[
old
,
newer
,
older
,
noDeploy
]
=
mockItems
;
expect
(
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
newer
,
old
,
older
,
noDeploy
]);
expect
(
wrapper
.
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
newer
,
old
,
older
,
noDeploy
]);
});
});
it
(
'
should push environments with no deployments to the bottom
'
,
()
=>
{
it
(
'
should push environments with no deployments to the bottom
'
,
()
=>
{
...
@@ -137,15 +139,17 @@ describe('Environment table', () => {
...
@@ -137,15 +139,17 @@ describe('Environment table', () => {
},
},
];
];
vm
=
mountComponent
(
Component
,
{
factory
({
propsData
:
{
environments
:
mockItems
,
environments
:
mockItems
,
canReadEnvironment
:
true
,
canReadEnvironment
:
true
,
...
eeOnlyProps
,
...
eeOnlyProps
,
},
});
});
const
[
prod
,
review
,
staging
]
=
mockItems
;
const
[
prod
,
review
,
staging
]
=
mockItems
;
expect
(
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
review
,
staging
,
prod
]);
expect
(
wrapper
.
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
review
,
staging
,
prod
]);
});
});
it
(
'
should sort environments by folder first
'
,
()
=>
{
it
(
'
should sort environments by folder first
'
,
()
=>
{
...
@@ -174,15 +178,17 @@ describe('Environment table', () => {
...
@@ -174,15 +178,17 @@ describe('Environment table', () => {
},
},
];
];
vm
=
mountComponent
(
Component
,
{
factory
({
propsData
:
{
environments
:
mockItems
,
environments
:
mockItems
,
canReadEnvironment
:
true
,
canReadEnvironment
:
true
,
...
eeOnlyProps
,
...
eeOnlyProps
,
},
});
});
const
[
old
,
newer
,
older
]
=
mockItems
;
const
[
old
,
newer
,
older
]
=
mockItems
;
expect
(
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
older
,
newer
,
old
]);
expect
(
wrapper
.
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
older
,
newer
,
old
]);
});
});
it
(
'
should break ties by name
'
,
()
=>
{
it
(
'
should break ties by name
'
,
()
=>
{
...
@@ -201,15 +207,17 @@ describe('Environment table', () => {
...
@@ -201,15 +207,17 @@ describe('Environment table', () => {
},
},
];
];
vm
=
mountComponent
(
Component
,
{
factory
({
propsData
:
{
environments
:
mockItems
,
environments
:
mockItems
,
canReadEnvironment
:
true
,
canReadEnvironment
:
true
,
...
eeOnlyProps
,
...
eeOnlyProps
,
},
});
});
const
[
old
,
newer
,
older
]
=
mockItems
;
const
[
old
,
newer
,
older
]
=
mockItems
;
expect
(
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
older
,
newer
,
old
]);
expect
(
wrapper
.
vm
.
sortEnvironments
(
mockItems
)).
toEqual
([
older
,
newer
,
old
]);
});
});
});
});
...
@@ -250,19 +258,21 @@ describe('Environment table', () => {
...
@@ -250,19 +258,21 @@ describe('Environment table', () => {
const
[
production
,
review
,
staging
]
=
mockItems
;
const
[
production
,
review
,
staging
]
=
mockItems
;
const
[
addcibuildstatus
,
master
]
=
mockItems
[
1
].
children
;
const
[
addcibuildstatus
,
master
]
=
mockItems
[
1
].
children
;
vm
=
mountComponent
(
Component
,
{
factory
({
propsData
:
{
environments
:
mockItems
,
environments
:
mockItems
,
canReadEnvironment
:
true
,
canReadEnvironment
:
true
,
...
eeOnlyProps
,
...
eeOnlyProps
,
},
});
});
expect
(
vm
.
sortedEnvironments
.
map
(
env
=>
env
.
name
)).
toEqual
([
expect
(
wrapper
.
vm
.
sortedEnvironments
.
map
(
env
=>
env
.
name
)).
toEqual
([
review
.
name
,
review
.
name
,
staging
.
name
,
staging
.
name
,
production
.
name
,
production
.
name
,
]);
]);
expect
(
vm
.
sortedEnvironments
[
0
].
children
).
toEqual
([
master
,
addcibuildstatus
]);
expect
(
wrapper
.
vm
.
sortedEnvironments
[
0
].
children
).
toEqual
([
master
,
addcibuildstatus
]);
});
});
});
});
});
});
spec/frontend/environments/mock_data.js
0 → 100644
View file @
27d91a62
const
environment
=
{
name
:
'
production
'
,
size
:
1
,
state
:
'
stopped
'
,
external_url
:
'
http://external.com
'
,
environment_type
:
null
,
last_deployment
:
{
id
:
66
,
iid
:
6
,
sha
:
'
500aabcb17c97bdcf2d0c410b70cb8556f0362dd
'
,
ref
:
{
name
:
'
master
'
,
ref_url
:
'
root/ci-folders/tree/master
'
,
},
tag
:
true
,
'
last?
'
:
true
,
user
:
{
name
:
'
Administrator
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80
\
u0026d=identicon
'
,
web_url
:
'
http://localhost:3000/root
'
,
},
commit
:
{
id
:
'
500aabcb17c97bdcf2d0c410b70cb8556f0362dd
'
,
short_id
:
'
500aabcb
'
,
title
:
'
Update .gitlab-ci.yml
'
,
author_name
:
'
Administrator
'
,
author_email
:
'
admin@example.com
'
,
created_at
:
'
2016-11-07T18:28:13.000+00:00
'
,
message
:
'
Update .gitlab-ci.yml
'
,
author
:
{
name
:
'
Administrator
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80
\
u0026d=identicon
'
,
web_url
:
'
http://localhost:3000/root
'
,
},
commit_path
:
'
/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd
'
,
},
deployable
:
{
id
:
1279
,
name
:
'
deploy
'
,
build_path
:
'
/root/ci-folders/builds/1279
'
,
retry_path
:
'
/root/ci-folders/builds/1279/retry
'
,
created_at
:
'
2016-11-29T18:11:58.430Z
'
,
updated_at
:
'
2016-11-29T18:11:58.430Z
'
,
},
manual_actions
:
[
{
name
:
'
action
'
,
play_path
:
'
/play
'
,
},
],
deployed_at
:
'
2016-11-29T18:11:58.430Z
'
,
},
has_stop_action
:
true
,
environment_path
:
'
root/ci-folders/environments/31
'
,
log_path
:
'
root/ci-folders/environments/31/logs
'
,
created_at
:
'
2016-11-07T11:11:16.525Z
'
,
updated_at
:
'
2016-11-10T15:55:58.778Z
'
,
};
const
folder
=
{
name
:
'
review
'
,
folderName
:
'
review
'
,
size
:
3
,
isFolder
:
true
,
environment_path
:
'
url
'
,
log_path
:
'
url
'
,
latest
:
{
environment_path
:
'
url
'
,
},
};
const
tableData
=
{
name
:
{
title
:
'
Environment
'
,
spacing
:
'
section-15
'
,
},
deploy
:
{
title
:
'
Deployment
'
,
spacing
:
'
section-10
'
,
},
build
:
{
title
:
'
Job
'
,
spacing
:
'
section-15
'
,
},
commit
:
{
title
:
'
Commit
'
,
spacing
:
'
section-20
'
,
},
date
:
{
title
:
'
Updated
'
,
spacing
:
'
section-10
'
,
},
actions
:
{
spacing
:
'
section-25
'
,
},
};
export
{
environment
,
folder
,
tableData
};
spec/javascripts/environments/environment_item_spec.js
deleted
100644 → 0
View file @
5e11c9b7
import
{
format
}
from
'
timeago.js
'
;
import
Vue
from
'
vue
'
;
import
environmentItemComp
from
'
~/environments/components/environment_item.vue
'
;
const
tableData
=
{
name
:
{
title
:
'
Environment
'
,
spacing
:
'
section-15
'
,
},
deploy
:
{
title
:
'
Deployment
'
,
spacing
:
'
section-10
'
,
},
build
:
{
title
:
'
Job
'
,
spacing
:
'
section-15
'
,
},
commit
:
{
title
:
'
Commit
'
,
spacing
:
'
section-20
'
,
},
date
:
{
title
:
'
Updated
'
,
spacing
:
'
section-10
'
,
},
actions
:
{
spacing
:
'
section-25
'
,
},
};
describe
(
'
Environment item
'
,
()
=>
{
let
EnvironmentItem
;
beforeEach
(()
=>
{
EnvironmentItem
=
Vue
.
extend
(
environmentItemComp
);
});
describe
(
'
When item is folder
'
,
()
=>
{
let
mockItem
;
let
component
;
beforeEach
(()
=>
{
mockItem
=
{
name
:
'
review
'
,
folderName
:
'
review
'
,
size
:
3
,
isFolder
:
true
,
environment_path
:
'
url
'
,
log_path
:
'
url
'
,
};
component
=
new
EnvironmentItem
({
propsData
:
{
model
:
mockItem
,
canReadEnvironment
:
true
,
tableData
,
},
}).
$mount
();
});
it
(
'
should render folder icon and name
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.folder-name
'
).
textContent
).
toContain
(
mockItem
.
name
);
expect
(
component
.
$el
.
querySelector
(
'
.folder-icon
'
)).
toBeDefined
();
});
it
(
'
should render the number of children in a badge
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.folder-name .badge
'
).
textContent
).
toContain
(
mockItem
.
size
,
);
});
});
describe
(
'
when item is not folder
'
,
()
=>
{
let
environment
;
let
component
;
beforeEach
(()
=>
{
environment
=
{
name
:
'
production
'
,
size
:
1
,
state
:
'
stopped
'
,
external_url
:
'
http://external.com
'
,
environment_type
:
null
,
last_deployment
:
{
id
:
66
,
iid
:
6
,
sha
:
'
500aabcb17c97bdcf2d0c410b70cb8556f0362dd
'
,
ref
:
{
name
:
'
master
'
,
ref_url
:
'
root/ci-folders/tree/master
'
,
},
tag
:
true
,
'
last?
'
:
true
,
user
:
{
name
:
'
Administrator
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80
\
u0026d=identicon
'
,
web_url
:
'
http://localhost:3000/root
'
,
},
commit
:
{
id
:
'
500aabcb17c97bdcf2d0c410b70cb8556f0362dd
'
,
short_id
:
'
500aabcb
'
,
title
:
'
Update .gitlab-ci.yml
'
,
author_name
:
'
Administrator
'
,
author_email
:
'
admin@example.com
'
,
created_at
:
'
2016-11-07T18:28:13.000+00:00
'
,
message
:
'
Update .gitlab-ci.yml
'
,
author
:
{
name
:
'
Administrator
'
,
username
:
'
root
'
,
id
:
1
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80
\
u0026d=identicon
'
,
web_url
:
'
http://localhost:3000/root
'
,
},
commit_path
:
'
/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd
'
,
},
deployable
:
{
id
:
1279
,
name
:
'
deploy
'
,
build_path
:
'
/root/ci-folders/builds/1279
'
,
retry_path
:
'
/root/ci-folders/builds/1279/retry
'
,
created_at
:
'
2016-11-29T18:11:58.430Z
'
,
updated_at
:
'
2016-11-29T18:11:58.430Z
'
,
},
manual_actions
:
[
{
name
:
'
action
'
,
play_path
:
'
/play
'
,
},
],
deployed_at
:
'
2016-11-29T18:11:58.430Z
'
,
},
has_stop_action
:
true
,
environment_path
:
'
root/ci-folders/environments/31
'
,
log_path
:
'
root/ci-folders/environments/31/logs
'
,
created_at
:
'
2016-11-07T11:11:16.525Z
'
,
updated_at
:
'
2016-11-10T15:55:58.778Z
'
,
};
component
=
new
EnvironmentItem
({
propsData
:
{
model
:
environment
,
canReadEnvironment
:
true
,
tableData
,
},
}).
$mount
();
});
it
(
'
should render environment name
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.environment-name
'
).
textContent
).
toContain
(
environment
.
name
,
);
});
describe
(
'
With deployment
'
,
()
=>
{
it
(
'
should render deployment internal id
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.deployment-column span
'
).
textContent
).
toContain
(
environment
.
last_deployment
.
iid
,
);
expect
(
component
.
$el
.
querySelector
(
'
.deployment-column span
'
).
textContent
).
toContain
(
'
#
'
);
});
it
(
'
should render last deployment date
'
,
()
=>
{
const
formatedDate
=
format
(
environment
.
last_deployment
.
deployed_at
);
expect
(
component
.
$el
.
querySelector
(
'
.environment-created-date-timeago
'
).
textContent
,
).
toContain
(
formatedDate
);
});
describe
(
'
With user information
'
,
()
=>
{
it
(
'
should render user avatar with link to profile
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-deploy-user-container
'
).
getAttribute
(
'
href
'
),
).
toEqual
(
environment
.
last_deployment
.
user
.
web_url
);
});
});
describe
(
'
With build url
'
,
()
=>
{
it
(
'
should link to build url provided
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.build-link
'
).
getAttribute
(
'
href
'
)).
toEqual
(
environment
.
last_deployment
.
deployable
.
build_path
,
);
});
it
(
'
should render deployable name and id
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.build-link
'
).
getAttribute
(
'
href
'
)).
toEqual
(
environment
.
last_deployment
.
deployable
.
build_path
,
);
});
});
describe
(
'
With commit information
'
,
()
=>
{
it
(
'
should render commit component
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-commit-component
'
)).
toBeDefined
();
});
});
});
describe
(
'
With manual actions
'
,
()
=>
{
it
(
'
should render actions component
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-manual-actions-container
'
)).
toBeDefined
();
});
});
describe
(
'
With external URL
'
,
()
=>
{
it
(
'
should render external url component
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-external-url-container
'
)).
toBeDefined
();
});
});
describe
(
'
With stop action
'
,
()
=>
{
it
(
'
should render stop action component
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-stop-component-container
'
)).
toBeDefined
();
});
});
describe
(
'
With retry action
'
,
()
=>
{
it
(
'
should render rollback component
'
,
()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.js-rollback-component-container
'
)).
toBeDefined
();
});
});
});
});
spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js
View file @
27d91a62
...
@@ -35,4 +35,8 @@ describe('Linked Pipelines Column', () => {
...
@@ -35,4 +35,8 @@ describe('Linked Pipelines Column', () => {
expect
(
linkedPipelineElements
.
length
).
toBe
(
props
.
linkedPipelines
.
length
);
expect
(
linkedPipelineElements
.
length
).
toBe
(
props
.
linkedPipelines
.
length
);
});
});
it
(
'
renders cross project triangle when column is upstream
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.cross-project-triangle
'
)).
toBeDefined
();
});
});
});
spec/lib/banzai/reference_parser/mentioned_
users_by_
group_parser_spec.rb
→
spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
View file @
27d91a62
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
require
'spec_helper'
require
'spec_helper'
describe
Banzai
::
ReferenceParser
::
Mentioned
UsersBy
GroupParser
do
describe
Banzai
::
ReferenceParser
::
MentionedGroupParser
do
include
ReferenceParserHelpers
include
ReferenceParserHelpers
let
(
:group
)
{
create
(
:group
,
:private
)
}
let
(
:group
)
{
create
(
:group
,
:private
)
}
...
...
spec/lib/banzai/reference_parser/mentioned_
users_by_
project_parser_spec.rb
→
spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
View file @
27d91a62
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
require
'spec_helper'
require
'spec_helper'
describe
Banzai
::
ReferenceParser
::
Mentioned
UsersBy
ProjectParser
do
describe
Banzai
::
ReferenceParser
::
MentionedProjectParser
do
include
ReferenceParserHelpers
include
ReferenceParserHelpers
let
(
:group
)
{
create
(
:group
,
:private
)
}
let
(
:group
)
{
create
(
:group
,
:private
)
}
...
...
spec/lib/gitlab/ci/config/entry/default_spec.rb
View file @
27d91a62
...
@@ -27,7 +27,7 @@ describe Gitlab::Ci::Config::Entry::Default do
...
@@ -27,7 +27,7 @@ describe Gitlab::Ci::Config::Entry::Default do
expect
(
described_class
.
nodes
.
keys
)
expect
(
described_class
.
nodes
.
keys
)
.
to
match_array
(
%i[before_script image services
.
to
match_array
(
%i[before_script image services
after_script cache interruptible
after_script cache interruptible
timeout retry]
)
timeout retry
tags
]
)
end
end
end
end
end
end
...
...
spec/lib/gitlab/ci/config/entry/job_spec.rb
View file @
27d91a62
...
@@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::Entry::Job do
...
@@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::Entry::Job do
let
(
:result
)
do
let
(
:result
)
do
%i[before_script script stage type after_script cache
%i[before_script script stage type after_script cache
image services only except rules needs variables artifacts
image services only except rules needs variables artifacts
environment coverage retry interruptible timeout]
environment coverage retry interruptible timeout
tags
]
end
end
it
{
is_expected
.
to
match_array
result
}
it
{
is_expected
.
to
match_array
result
}
...
...
spec/lib/gitlab/ci/yaml_processor_spec.rb
View file @
27d91a62
...
@@ -1849,7 +1849,7 @@ module Gitlab
...
@@ -1849,7 +1849,7 @@ module Gitlab
config
=
YAML
.
dump
({
rspec:
{
script:
"test"
,
tags:
"mysql"
}
})
config
=
YAML
.
dump
({
rspec:
{
script:
"test"
,
tags:
"mysql"
}
})
expect
do
expect
do
Gitlab
::
Ci
::
YamlProcessor
.
new
(
config
)
Gitlab
::
Ci
::
YamlProcessor
.
new
(
config
)
end
.
to
raise_error
(
Gitlab
::
Ci
::
YamlProcessor
::
ValidationError
,
"jobs:rspec
tags
should be an array of strings"
)
end
.
to
raise_error
(
Gitlab
::
Ci
::
YamlProcessor
::
ValidationError
,
"jobs:rspec
:tags config
should be an array of strings"
)
end
end
it
"returns errors if before_script parameter is invalid"
do
it
"returns errors if before_script parameter is invalid"
do
...
@@ -2197,7 +2197,7 @@ module Gitlab
...
@@ -2197,7 +2197,7 @@ module Gitlab
context
"when the tags parameter is invalid"
do
context
"when the tags parameter is invalid"
do
let
(
:content
)
{
YAML
.
dump
({
rspec:
{
script:
"test"
,
tags:
"mysql"
}
})
}
let
(
:content
)
{
YAML
.
dump
({
rspec:
{
script:
"test"
,
tags:
"mysql"
}
})
}
it
{
is_expected
.
to
eq
"jobs:rspec
tags
should be an array of strings"
}
it
{
is_expected
.
to
eq
"jobs:rspec
:tags config
should be an array of strings"
}
end
end
context
"when YAML content is empty"
do
context
"when YAML content is empty"
do
...
...
spec/lib/gitlab/import_export/all_models.yml
View file @
27d91a62
...
@@ -34,6 +34,7 @@ issues:
...
@@ -34,6 +34,7 @@ issues:
-
zoom_meetings
-
zoom_meetings
-
vulnerability_links
-
vulnerability_links
-
related_vulnerabilities
-
related_vulnerabilities
-
user_mentions
events
:
events
:
-
author
-
author
-
project
-
project
...
@@ -82,6 +83,7 @@ snippets:
...
@@ -82,6 +83,7 @@ snippets:
-
notes
-
notes
-
award_emoji
-
award_emoji
-
user_agent_detail
-
user_agent_detail
-
user_mentions
releases
:
releases
:
-
author
-
author
-
project
-
project
...
@@ -142,6 +144,7 @@ merge_requests:
...
@@ -142,6 +144,7 @@ merge_requests:
-
description_versions
-
description_versions
-
deployment_merge_requests
-
deployment_merge_requests
-
deployments
-
deployments
-
user_mentions
external_pull_requests
:
external_pull_requests
:
-
project
-
project
merge_request_diff
:
merge_request_diff
:
...
@@ -539,6 +542,7 @@ design: &design
...
@@ -539,6 +542,7 @@ design: &design
-
actions
-
actions
-
versions
-
versions
-
notes
-
notes
-
user_mentions
designs
:
*design
designs
:
*design
actions
:
actions
:
-
design
-
design
...
...
spec/models/broadcast_message_spec.rb
View file @
27d91a62
...
@@ -20,65 +20,71 @@ describe BroadcastMessage do
...
@@ -20,65 +20,71 @@ describe BroadcastMessage do
it
{
is_expected
.
to
allow_value
(
triplet
).
for
(
:font
)
}
it
{
is_expected
.
to
allow_value
(
triplet
).
for
(
:font
)
}
it
{
is_expected
.
to
allow_value
(
hex
).
for
(
:font
)
}
it
{
is_expected
.
to
allow_value
(
hex
).
for
(
:font
)
}
it
{
is_expected
.
not_to
allow_value
(
'000'
).
for
(
:font
)
}
it
{
is_expected
.
not_to
allow_value
(
'000'
).
for
(
:font
)
}
it
{
is_expected
.
to
allow_value
(
1
).
for
(
:broadcast_type
)
}
it
{
is_expected
.
not_to
allow_value
(
nil
).
for
(
:broadcast_type
)
}
end
end
describe
'.current'
,
:use_clean_rails_memory_store_caching
do
shared_examples
'time constrainted'
do
|
broadcast_type
|
it
'returns message if time match'
do
it
'returns message if time match'
do
message
=
create
(
:broadcast_message
)
message
=
create
(
:broadcast_message
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
).
to
include
(
message
)
expect
(
subject
.
call
).
to
include
(
message
)
end
end
it
'returns multiple messages if time match'
do
it
'returns multiple messages if time match'
do
message1
=
create
(
:broadcast_message
)
message1
=
create
(
:broadcast_message
,
broadcast_type:
broadcast_type
)
message2
=
create
(
:broadcast_message
)
message2
=
create
(
:broadcast_message
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
).
to
contain_exactly
(
message1
,
message2
)
expect
(
subject
.
call
).
to
contain_exactly
(
message1
,
message2
)
end
end
it
'returns empty list if time not come'
do
it
'returns empty list if time not come'
do
create
(
:broadcast_message
,
:future
)
create
(
:broadcast_message
,
:future
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
).
to
be_empty
expect
(
subject
.
call
).
to
be_empty
end
end
it
'returns empty list if time has passed'
do
it
'returns empty list if time has passed'
do
create
(
:broadcast_message
,
:expired
)
create
(
:broadcast_message
,
:expired
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
).
to
be_empty
expect
(
subject
.
call
).
to
be_empty
end
end
end
shared_examples
'message cache'
do
|
broadcast_type
|
it
'caches the output of the query for two weeks'
do
it
'caches the output of the query for two weeks'
do
create
(
:broadcast_message
)
create
(
:broadcast_message
,
broadcast_type:
broadcast_type
)
expect
(
described_class
).
to
receive
(
:current_and_future_messages
).
and_call_original
.
twice
expect
(
described_class
).
to
receive
(
:current_and_future_messages
).
and_call_original
.
twice
described_class
.
current
subject
.
call
Timecop
.
travel
(
3
.
weeks
)
do
Timecop
.
travel
(
3
.
weeks
)
do
described_class
.
current
subject
.
call
end
end
end
end
it
'does not create new records'
do
it
'does not create new records'
do
create
(
:broadcast_message
)
create
(
:broadcast_message
,
broadcast_type:
broadcast_type
)
expect
{
described_class
.
current
}.
not_to
change
{
described_class
.
count
}
expect
{
subject
.
call
}.
not_to
change
{
described_class
.
count
}
end
end
it
'includes messages that need to be displayed in the future'
do
it
'includes messages that need to be displayed in the future'
do
create
(
:broadcast_message
)
create
(
:broadcast_message
,
broadcast_type:
broadcast_type
)
future
=
create
(
future
=
create
(
:broadcast_message
,
:broadcast_message
,
starts_at:
Time
.
now
+
10
.
minutes
,
starts_at:
Time
.
now
+
10
.
minutes
,
ends_at:
Time
.
now
+
20
.
minutes
ends_at:
Time
.
now
+
20
.
minutes
,
broadcast_type:
broadcast_type
)
)
expect
(
described_class
.
current
.
length
).
to
eq
(
1
)
expect
(
subject
.
call
.
length
).
to
eq
(
1
)
Timecop
.
travel
(
future
.
starts_at
)
do
Timecop
.
travel
(
future
.
starts_at
)
do
expect
(
described_class
.
current
.
length
).
to
eq
(
2
)
expect
(
subject
.
call
.
length
).
to
eq
(
2
)
end
end
end
end
...
@@ -86,43 +92,90 @@ describe BroadcastMessage do
...
@@ -86,43 +92,90 @@ describe BroadcastMessage do
create
(
:broadcast_message
,
:future
)
create
(
:broadcast_message
,
:future
)
expect
(
Rails
.
cache
).
not_to
receive
(
:delete
).
with
(
described_class
::
CACHE_KEY
)
expect
(
Rails
.
cache
).
not_to
receive
(
:delete
).
with
(
described_class
::
CACHE_KEY
)
expect
(
described_class
.
current
.
length
).
to
eq
(
0
)
expect
(
subject
.
call
.
length
).
to
eq
(
0
)
end
end
end
shared_examples
"matches with current path"
do
|
broadcast_type
|
it
'returns message if it matches the target path'
do
it
'returns message if it matches the target path'
do
message
=
create
(
:broadcast_message
,
target_path:
"*/onboarding_completed"
)
message
=
create
(
:broadcast_message
,
target_path:
"*/onboarding_completed"
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
(
'/users/onboarding_completed'
)).
to
include
(
message
)
expect
(
subject
.
call
(
'/users/onboarding_completed'
)).
to
include
(
message
)
end
end
it
'returns message if part of the target path matches'
do
it
'returns message if part of the target path matches'
do
create
(
:broadcast_message
,
target_path:
"/users/*/issues"
)
create
(
:broadcast_message
,
target_path:
"/users/*/issues"
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
(
'/users/name/issues'
).
length
).
to
eq
(
1
)
expect
(
subject
.
call
(
'/users/name/issues'
).
length
).
to
eq
(
1
)
end
end
it
'returns the message for empty target path'
do
it
'returns the message for empty target path'
do
create
(
:broadcast_message
,
target_path:
""
)
create
(
:broadcast_message
,
target_path:
""
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
(
'/users/name/issues'
).
length
).
to
eq
(
1
)
expect
(
subject
.
call
(
'/users/name/issues'
).
length
).
to
eq
(
1
)
end
end
it
'returns the message if target path is nil'
do
it
'returns the message if target path is nil'
do
create
(
:broadcast_message
,
target_path:
nil
)
create
(
:broadcast_message
,
target_path:
nil
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
(
'/users/name/issues'
).
length
).
to
eq
(
1
)
expect
(
subject
.
call
(
'/users/name/issues'
).
length
).
to
eq
(
1
)
end
end
it
'does not return message if target path does not match'
do
it
'does not return message if target path does not match'
do
create
(
:broadcast_message
,
target_path:
"/onboarding_completed"
)
create
(
:broadcast_message
,
target_path:
"/onboarding_completed"
,
broadcast_type:
broadcast_type
)
expect
(
described_class
.
current
(
'/welcome'
).
length
).
to
eq
(
0
)
expect
(
subject
.
call
(
'/welcome'
).
length
).
to
eq
(
0
)
end
end
it
'does not return message if target path does not match when using wildcard'
do
it
'does not return message if target path does not match when using wildcard'
do
create
(
:broadcast_message
,
target_path:
"/users/*/issues"
)
create
(
:broadcast_message
,
target_path:
"/users/*/issues"
,
broadcast_type:
broadcast_type
)
expect
(
subject
.
call
(
'/group/groupname/issues'
).
length
).
to
eq
(
0
)
end
end
describe
'.current'
,
:use_clean_rails_memory_store_caching
do
subject
{
->
(
path
=
nil
)
{
described_class
.
current
(
path
)
}
}
it_behaves_like
'time constrainted'
,
:banner
it_behaves_like
'message cache'
,
:banner
it_behaves_like
'matches with current path'
,
:banner
it
'returns both types'
do
banner_message
=
create
(
:broadcast_message
,
broadcast_type: :banner
)
notification_message
=
create
(
:broadcast_message
,
broadcast_type: :notification
)
expect
(
subject
.
call
).
to
contain_exactly
(
banner_message
,
notification_message
)
end
end
describe
'.current_banner_messages'
,
:use_clean_rails_memory_store_caching
do
subject
{
->
(
path
=
nil
)
{
described_class
.
current_banner_messages
(
path
)
}
}
it_behaves_like
'time constrainted'
,
:banner
it_behaves_like
'message cache'
,
:banner
it_behaves_like
'matches with current path'
,
:banner
it
'only returns banners'
do
banner_message
=
create
(
:broadcast_message
,
broadcast_type: :banner
)
create
(
:broadcast_message
,
broadcast_type: :notification
)
expect
(
subject
.
call
).
to
contain_exactly
(
banner_message
)
end
end
describe
'.current_notification_messages'
,
:use_clean_rails_memory_store_caching
do
subject
{
->
(
path
=
nil
)
{
described_class
.
current_notification_messages
(
path
)
}
}
it_behaves_like
'time constrainted'
,
:notification
it_behaves_like
'message cache'
,
:notification
it_behaves_like
'matches with current path'
,
:notification
it
'only returns notifications'
do
notification_message
=
create
(
:broadcast_message
,
broadcast_type: :notification
)
create
(
:broadcast_message
,
broadcast_type: :banner
)
expect
(
described_class
.
current
(
'/group/groupname/issues'
).
length
).
to
eq
(
0
)
expect
(
subject
.
call
).
to
contain_exactly
(
notification_message
)
end
end
end
end
...
@@ -193,6 +246,8 @@ describe BroadcastMessage do
...
@@ -193,6 +246,8 @@ describe BroadcastMessage do
message
=
create
(
:broadcast_message
)
message
=
create
(
:broadcast_message
)
expect
(
Rails
.
cache
).
to
receive
(
:delete
).
with
(
described_class
::
CACHE_KEY
)
expect
(
Rails
.
cache
).
to
receive
(
:delete
).
with
(
described_class
::
CACHE_KEY
)
expect
(
Rails
.
cache
).
to
receive
(
:delete
).
with
(
described_class
::
BANNER_CACHE_KEY
)
expect
(
Rails
.
cache
).
to
receive
(
:delete
).
with
(
described_class
::
NOTIFICATION_CACHE_KEY
)
message
.
flush_redis_cache
message
.
flush_redis_cache
end
end
...
...
spec/models/concerns/mentionable_spec.rb
View file @
27d91a62
...
@@ -166,6 +166,21 @@ describe Issue, "Mentionable" do
...
@@ -166,6 +166,21 @@ describe Issue, "Mentionable" do
create
(
:issue
,
project:
project
,
description:
description
,
author:
author
)
create
(
:issue
,
project:
project
,
description:
description
,
author:
author
)
end
end
end
end
describe
'#store_mentions!'
do
it_behaves_like
'mentions in description'
,
:issue
it_behaves_like
'mentions in notes'
,
:issue
do
let
(
:note
)
{
create
(
:note_on_issue
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
describe
'load mentions'
do
it_behaves_like
'load mentions from DB'
,
:issue
do
let
(
:note
)
{
create
(
:note_on_issue
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
end
end
describe
Commit
,
'Mentionable'
do
describe
Commit
,
'Mentionable'
do
...
@@ -221,4 +236,56 @@ describe Commit, 'Mentionable' do
...
@@ -221,4 +236,56 @@ describe Commit, 'Mentionable' do
end
end
end
end
end
end
describe
'#store_mentions!'
do
it_behaves_like
'mentions in notes'
,
:commit
do
let
(
:note
)
{
create
(
:note_on_commit
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
describe
'load mentions'
do
it_behaves_like
'load mentions from DB'
,
:commit
do
let
(
:note
)
{
create
(
:note_on_commit
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
end
describe
MergeRequest
,
'Mentionable'
do
describe
'#store_mentions!'
do
it_behaves_like
'mentions in description'
,
:merge_request
it_behaves_like
'mentions in notes'
,
:merge_request
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
let
(
:note
)
{
create
(
:note_on_merge_request
,
noteable:
merge_request
,
project:
merge_request
.
project
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
describe
'load mentions'
do
it_behaves_like
'load mentions from DB'
,
:merge_request
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
let
(
:note
)
{
create
(
:note_on_merge_request
,
noteable:
merge_request
,
project:
merge_request
.
project
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
end
describe
Snippet
,
'Mentionable'
do
describe
'#store_mentions!'
do
it_behaves_like
'mentions in description'
,
:project_snippet
it_behaves_like
'mentions in notes'
,
:project_snippet
do
let
(
:note
)
{
create
(
:note_on_project_snippet
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
describe
'load mentions'
do
it_behaves_like
'load mentions from DB'
,
:project_snippet
do
let
(
:note
)
{
create
(
:note_on_project_snippet
)
}
let
(
:mentionable
)
{
note
.
noteable
}
end
end
end
end
spec/models/issue_spec.rb
View file @
27d91a62
...
@@ -12,6 +12,7 @@ describe Issue do
...
@@ -12,6 +12,7 @@ describe Issue do
it
{
is_expected
.
to
belong_to
(
:duplicated_to
).
class_name
(
'Issue'
)
}
it
{
is_expected
.
to
belong_to
(
:duplicated_to
).
class_name
(
'Issue'
)
}
it
{
is_expected
.
to
belong_to
(
:closed_by
).
class_name
(
'User'
)
}
it
{
is_expected
.
to
belong_to
(
:closed_by
).
class_name
(
'User'
)
}
it
{
is_expected
.
to
have_many
(
:assignees
)
}
it
{
is_expected
.
to
have_many
(
:assignees
)
}
it
{
is_expected
.
to
have_many
(
:user_mentions
).
class_name
(
"IssueUserMention"
)
}
it
{
is_expected
.
to
have_one
(
:sentry_issue
)
}
it
{
is_expected
.
to
have_one
(
:sentry_issue
)
}
end
end
...
...
spec/models/merge_request_spec.rb
View file @
27d91a62
...
@@ -17,6 +17,7 @@ describe MergeRequest do
...
@@ -17,6 +17,7 @@ describe MergeRequest do
it
{
is_expected
.
to
belong_to
(
:merge_user
).
class_name
(
"User"
)
}
it
{
is_expected
.
to
belong_to
(
:merge_user
).
class_name
(
"User"
)
}
it
{
is_expected
.
to
have_many
(
:assignees
).
through
(
:merge_request_assignees
)
}
it
{
is_expected
.
to
have_many
(
:assignees
).
through
(
:merge_request_assignees
)
}
it
{
is_expected
.
to
have_many
(
:merge_request_diffs
)
}
it
{
is_expected
.
to
have_many
(
:merge_request_diffs
)
}
it
{
is_expected
.
to
have_many
(
:user_mentions
).
class_name
(
"MergeRequestUserMention"
)
}
context
'for forks'
do
context
'for forks'
do
let!
(
:project
)
{
create
(
:project
)
}
let!
(
:project
)
{
create
(
:project
)
}
...
...
spec/models/snippet_spec.rb
View file @
27d91a62
...
@@ -18,6 +18,7 @@ describe Snippet do
...
@@ -18,6 +18,7 @@ describe Snippet do
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
have_many
(
:notes
).
dependent
(
:destroy
)
}
it
{
is_expected
.
to
have_many
(
:notes
).
dependent
(
:destroy
)
}
it
{
is_expected
.
to
have_many
(
:award_emoji
).
dependent
(
:destroy
)
}
it
{
is_expected
.
to
have_many
(
:award_emoji
).
dependent
(
:destroy
)
}
it
{
is_expected
.
to
have_many
(
:user_mentions
).
class_name
(
"SnippetUserMention"
)
}
end
end
describe
'validation'
do
describe
'validation'
do
...
...
spec/models/user_mentions/commit_user_mention_spec.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
require
'spec_helper'
describe
CommitUserMention
do
describe
'associations'
do
it
{
is_expected
.
to
belong_to
(
:note
)
}
end
it_behaves_like
'has user mentions'
end
spec/models/user_mentions/issue_user_mention_spec.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
require
'spec_helper'
describe
IssueUserMention
do
describe
'associations'
do
it
{
is_expected
.
to
belong_to
(
:issue
)
}
it
{
is_expected
.
to
belong_to
(
:note
)
}
end
it_behaves_like
'has user mentions'
end
spec/models/user_mentions/merge_request_user_mention_spec.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
require
'spec_helper'
describe
MergeRequestUserMention
do
describe
'associations'
do
it
{
is_expected
.
to
belong_to
(
:merge_request
)
}
it
{
is_expected
.
to
belong_to
(
:note
)
}
end
it_behaves_like
'has user mentions'
end
spec/models/user_mentions/snippet_user_mention_spec.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
require
'spec_helper'
describe
SnippetUserMention
do
describe
'associations'
do
it
{
is_expected
.
to
belong_to
(
:snippet
)
}
it
{
is_expected
.
to
belong_to
(
:note
)
}
end
it_behaves_like
'has user mentions'
end
spec/support/shared_examples/mentionable_shared_examples.rb
View file @
27d91a62
...
@@ -195,3 +195,153 @@ shared_examples 'an editable mentionable' do
...
@@ -195,3 +195,153 @@ shared_examples 'an editable mentionable' do
subject
.
create_new_cross_references!
(
author
)
subject
.
create_new_cross_references!
(
author
)
end
end
end
end
shared_examples_for
'mentions in description'
do
|
mentionable_type
|
describe
'when store_mentioned_users_to_db feature disabled'
do
before
do
stub_feature_flags
(
store_mentioned_users_to_db:
false
)
mentionable
.
store_mentions!
end
context
'when mentionable description contains mentions'
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:mentionable
)
{
create
(
mentionable_type
,
description:
"
#{
user
.
to_reference
}
some description"
)
}
it
'stores no mentions'
do
expect
(
mentionable
.
user_mentions
.
count
).
to
eq
0
end
end
end
describe
'when store_mentioned_users_to_db feature enabled'
do
before
do
stub_feature_flags
(
store_mentioned_users_to_db:
true
)
mentionable
.
store_mentions!
end
context
'when mentionable description has no mentions'
do
let
(
:mentionable
)
{
create
(
mentionable_type
,
description:
"just some description"
)
}
it
'stores no mentions'
do
expect
(
mentionable
.
user_mentions
.
count
).
to
eq
0
end
end
context
'when mentionable description contains mentions'
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:mentionable_desc
)
{
"
#{
user
.
to_reference
}
some description
#{
group
.
to_reference
(
full:
true
)
}
and @all"
}
let
(
:mentionable
)
{
create
(
mentionable_type
,
description:
mentionable_desc
)
}
it
'stores mentions'
do
add_member
(
user
)
expect
(
mentionable
.
user_mentions
.
count
).
to
eq
1
expect
(
mentionable
.
referenced_users
).
to
match_array
([
user
])
expect
(
mentionable
.
referenced_projects
(
user
)).
to
match_array
([
mentionable
.
project
].
compact
)
# epic.project is nil, and we want empty []
expect
(
mentionable
.
referenced_groups
(
user
)).
to
match_array
([
group
])
end
end
end
end
shared_examples_for
'mentions in notes'
do
|
mentionable_type
|
context
'when mentionable notes contain mentions'
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:note_desc
)
{
"
#{
user
.
to_reference
}
and
#{
group
.
to_reference
(
full:
true
)
}
and @all"
}
let!
(
:mentionable
)
{
note
.
noteable
}
before
do
note
.
update
(
note:
note_desc
)
note
.
store_mentions!
add_member
(
user
)
end
it
'returns all mentionable mentions'
do
expect
(
mentionable
.
user_mentions
.
count
).
to
eq
1
expect
(
mentionable
.
referenced_users
).
to
eq
[
user
]
expect
(
mentionable
.
referenced_projects
(
user
)).
to
eq
[
mentionable
.
project
].
compact
# epic.project is nil, and we want empty []
expect
(
mentionable
.
referenced_groups
(
user
)).
to
eq
[
group
]
end
end
end
shared_examples_for
'load mentions from DB'
do
|
mentionable_type
|
context
'load stored mentions'
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:mentioned_user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:note_desc
)
{
"
#{
mentioned_user
.
to_reference
}
and
#{
group
.
to_reference
(
full:
true
)
}
and @all"
}
before
do
note
.
update
(
note:
note_desc
)
note
.
store_mentions!
add_member
(
user
)
end
context
'when stored user mention contains ids of inexistent records'
do
before
do
user_mention
=
note
.
send
(
:model_user_mention
)
mention_ids
=
{
mentioned_users_ids:
user_mention
.
mentioned_users_ids
.
to_a
<<
User
.
maximum
(
:id
).
to_i
.
succ
,
mentioned_projects_ids:
user_mention
.
mentioned_projects_ids
.
to_a
<<
Project
.
maximum
(
:id
).
to_i
.
succ
,
mentioned_groups_ids:
user_mention
.
mentioned_groups_ids
.
to_a
<<
Group
.
maximum
(
:id
).
to_i
.
succ
}
user_mention
.
update
(
mention_ids
)
end
it
'filters out inexistent mentions'
do
expect
(
mentionable
.
referenced_users
).
to
match_array
([
mentioned_user
])
expect
(
mentionable
.
referenced_projects
(
user
)).
to
match_array
([
mentionable
.
project
].
compact
)
# epic.project is nil, and we want empty []
expect
(
mentionable
.
referenced_groups
(
user
)).
to
match_array
([
group
])
end
end
context
'when private projects and groups are mentioned'
do
let
(
:mega_user
)
{
create
(
:user
)
}
let
(
:private_project
)
{
create
(
:project
,
:private
)
}
let
(
:project_member
)
{
create
(
:project_member
,
user:
create
(
:user
),
project:
private_project
)
}
let
(
:private_group
)
{
create
(
:group
,
:private
)
}
let
(
:group_member
)
{
create
(
:group_member
,
user:
create
(
:user
),
group:
private_group
)
}
before
do
user_mention
=
note
.
send
(
:model_user_mention
)
mention_ids
=
{
mentioned_projects_ids:
user_mention
.
mentioned_projects_ids
.
to_a
<<
private_project
.
id
,
mentioned_groups_ids:
user_mention
.
mentioned_groups_ids
.
to_a
<<
private_group
.
id
}
user_mention
.
update
(
mention_ids
)
add_member
(
mega_user
)
private_project
.
add_developer
(
mega_user
)
private_group
.
add_developer
(
mega_user
)
end
context
'when user has no access to some mentions'
do
it
'filters out inaccessible mentions'
do
expect
(
mentionable
.
referenced_projects
(
user
)).
to
match_array
([
mentionable
.
project
].
compact
)
# epic.project is nil, and we want empty []
expect
(
mentionable
.
referenced_groups
(
user
)).
to
match_array
([
group
])
end
end
context
'when user has access to all mentions'
do
it
'returns all mentions'
do
expect
(
mentionable
.
referenced_projects
(
mega_user
)).
to
match_array
([
mentionable
.
project
,
private_project
].
compact
)
# epic.project is nil, and we want empty []
expect
(
mentionable
.
referenced_groups
(
mega_user
)).
to
match_array
([
group
,
private_group
])
end
end
end
end
end
def
add_member
(
user
)
issuable_parent
=
if
mentionable
.
is_a?
(
Epic
)
mentionable
.
group
else
mentionable
.
project
end
issuable_parent
&
.
add_developer
(
user
)
end
spec/support/shared_examples/models/user_mentions_shared_examples.rb
0 → 100644
View file @
27d91a62
# frozen_string_literal: true
require
'spec_helper'
shared_examples_for
'has user mentions'
do
describe
'#has_mentions?'
do
context
'when no mentions'
do
it
'returns false'
do
expect
(
subject
.
mentioned_users_ids
).
to
be
nil
expect
(
subject
.
mentioned_projects_ids
).
to
be
nil
expect
(
subject
.
mentioned_groups_ids
).
to
be
nil
expect
(
subject
.
has_mentions?
).
to
be
false
end
end
context
'when mentioned_users_ids not null'
do
subject
{
described_class
.
new
(
mentioned_users_ids:
[
1
,
2
,
3
])
}
it
'returns true'
do
expect
(
subject
.
has_mentions?
).
to
be
true
end
end
context
'when mentioned projects'
do
subject
{
described_class
.
new
(
mentioned_projects_ids:
[
1
,
2
,
3
])
}
it
'returns true'
do
expect
(
subject
.
has_mentions?
).
to
be
true
end
end
context
'when mentioned groups'
do
subject
{
described_class
.
new
(
mentioned_groups_ids:
[
1
,
2
,
3
])
}
it
'returns true'
do
expect
(
subject
.
has_mentions?
).
to
be
true
end
end
end
end
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