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
f8cda25d
Commit
f8cda25d
authored
Dec 19, 2017
by
Kamil Trzcinski
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin-ee/master' into ee-fix-cluster-enviroment-missing
parents
be0369a1
a45b739c
Changes
69
Hide whitespace changes
Inline
Side-by-side
Showing
69 changed files
with
1339 additions
and
1317 deletions
+1339
-1317
.gitlab-ci.yml
.gitlab-ci.yml
+25
-20
app/assets/javascripts/boards/components/board_sidebar.js
app/assets/javascripts/boards/components/board_sidebar.js
+1
-1
app/assets/javascripts/boards/models/issue.js
app/assets/javascripts/boards/models/issue.js
+0
-1
app/assets/javascripts/diff_notes/components/diff_note_avatars.js
...ts/javascripts/diff_notes/components/diff_note_avatars.js
+2
-2
app/assets/javascripts/dispatcher.js
app/assets/javascripts/dispatcher.js
+3
-3
app/assets/javascripts/geo_nodes.js
app/assets/javascripts/geo_nodes.js
+2
-2
app/assets/javascripts/init_issuable_sidebar.js
app/assets/javascripts/init_issuable_sidebar.js
+2
-2
app/assets/javascripts/init_notes.js
app/assets/javascripts/init_notes.js
+4
-2
app/assets/javascripts/line_highlighter.js
app/assets/javascripts/line_highlighter.js
+1
-1
app/assets/javascripts/main.js
app/assets/javascripts/main.js
+0
-4
app/assets/javascripts/merge_request.js
app/assets/javascripts/merge_request.js
+128
-132
app/assets/javascripts/merge_request_tabs.js
app/assets/javascripts/merge_request_tabs.js
+2
-2
app/assets/javascripts/notes.js
app/assets/javascripts/notes.js
+6
-0
app/assets/javascripts/repo/components/repo_preview.vue
app/assets/javascripts/repo/components/repo_preview.vue
+1
-1
app/assets/javascripts/right_sidebar.js
app/assets/javascripts/right_sidebar.js
+220
-218
app/assets/javascripts/shortcuts_issuable.js
app/assets/javascripts/shortcuts_issuable.js
+2
-2
app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
...ts/vue_merge_request_widget/services/mr_widget_service.js
+1
-1
app/assets/stylesheets/framework/stacked-progress-bar.scss
app/assets/stylesheets/framework/stacked-progress-bar.scss
+3
-1
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+6
-2
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+2
-2
app/serializers/issuable_entity.rb
app/serializers/issuable_entity.rb
+0
-8
app/serializers/issuable_sidebar_entity.rb
app/serializers/issuable_sidebar_entity.rb
+1
-5
app/serializers/issue_entity.rb
app/serializers/issue_entity.rb
+8
-0
app/serializers/merge_request_serializer.rb
app/serializers/merge_request_serializer.rb
+3
-3
app/serializers/merge_request_widget_entity.rb
app/serializers/merge_request_widget_entity.rb
+2
-4
app/views/projects/merge_requests/show.html.haml
app/views/projects/merge_requests/show.html.haml
+1
-1
app/views/shared/empty_states/_issues.html.haml
app/views/shared/empty_states/_issues.html.haml
+7
-6
changelogs/unreleased-ee/4330-show-item-count-in-tooltip.yml
changelogs/unreleased-ee/4330-show-item-count-in-tooltip.yml
+5
-0
changelogs/unreleased/osw-isolate-mr-widget-exposed-attributes.yml
...s/unreleased/osw-isolate-mr-widget-exposed-attributes.yml
+5
-0
doc/articles/laravel_with_gitlab_and_envoy/index.md
doc/articles/laravel_with_gitlab_and_envoy/index.md
+1
-1
doc/workflow/repository_mirroring.md
doc/workflow/repository_mirroring.md
+65
-31
ee/app/serializers/ee/merge_request_widget_entity.rb
ee/app/serializers/ee/merge_request_widget_entity.rb
+1
-1
qa/qa.rb
qa/qa.rb
+21
-25
qa/qa/ee.rb
qa/qa/ee.rb
+6
-6
qa/qa/ee/factory/geo/node.rb
qa/qa/ee/factory/geo/node.rb
+3
-3
qa/qa/ee/factory/license.rb
qa/qa/ee/factory/license.rb
+19
-0
qa/qa/ee/scenario/license/add.rb
qa/qa/ee/scenario/license/add.rb
+0
-21
qa/qa/ee/scenario/test/geo.rb
qa/qa/ee/scenario/test/geo.rb
+3
-9
qa/qa/ee/strategy.rb
qa/qa/ee/strategy.rb
+1
-1
qa/qa/factory/base.rb
qa/qa/factory/base.rb
+16
-0
qa/qa/factory/repository/push.rb
qa/qa/factory/repository/push.rb
+45
-0
qa/qa/factory/resource/group.rb
qa/qa/factory/resource/group.rb
+23
-0
qa/qa/factory/resource/project.rb
qa/qa/factory/resource/project.rb
+40
-0
qa/qa/factory/resource/sandbox.rb
qa/qa/factory/resource/sandbox.rb
+28
-0
qa/qa/factory/settings/hashed_storage.rb
qa/qa/factory/settings/hashed_storage.rb
+22
-0
qa/qa/scenario/gitlab/admin/hashed_storage.rb
qa/qa/scenario/gitlab/admin/hashed_storage.rb
+0
-24
qa/qa/scenario/gitlab/group/create.rb
qa/qa/scenario/gitlab/group/create.rb
+0
-27
qa/qa/scenario/gitlab/project/create.rb
qa/qa/scenario/gitlab/project/create.rb
+0
-42
qa/qa/scenario/gitlab/repository/push.rb
qa/qa/scenario/gitlab/repository/push.rb
+0
-47
qa/qa/scenario/gitlab/sandbox/prepare.rb
qa/qa/scenario/gitlab/sandbox/prepare.rb
+0
-28
qa/qa/specs/features/ee/geo/replication_spec.rb
qa/qa/specs/features/ee/geo/replication_spec.rb
+3
-3
qa/qa/specs/features/project/create_spec.rb
qa/qa/specs/features/project/create_spec.rb
+1
-1
qa/qa/specs/features/repository/clone_spec.rb
qa/qa/specs/features/repository/clone_spec.rb
+1
-1
qa/qa/specs/features/repository/push_spec.rb
qa/qa/specs/features/repository/push_spec.rb
+2
-2
spec/controllers/projects/merge_requests_controller_spec.rb
spec/controllers/projects/merge_requests_controller_spec.rb
+4
-4
spec/ee/spec/serializers/epic_entity_spec.rb
spec/ee/spec/serializers/epic_entity_spec.rb
+1
-2
spec/ee/spec/serializers/merge_request_widget_entity_spec.rb
spec/ee/spec/serializers/merge_request_widget_entity_spec.rb
+1
-1
spec/features/issues_spec.rb
spec/features/issues_spec.rb
+538
-514
spec/features/merge_requests/mini_pipeline_graph_spec.rb
spec/features/merge_requests/mini_pipeline_graph_spec.rb
+4
-4
spec/fixtures/api/schemas/entities/merge_request_widget.json
spec/fixtures/api/schemas/entities/merge_request_widget.json
+4
-4
spec/javascripts/collapsed_sidebar_todo_spec.js
spec/javascripts/collapsed_sidebar_todo_spec.js
+1
-2
spec/javascripts/line_highlighter_spec.js
spec/javascripts/line_highlighter_spec.js
+1
-2
spec/javascripts/merge_request_notes_spec.js
spec/javascripts/merge_request_notes_spec.js
+1
-3
spec/javascripts/merge_request_spec.js
spec/javascripts/merge_request_spec.js
+1
-2
spec/javascripts/merge_request_tabs_spec.js
spec/javascripts/merge_request_tabs_spec.js
+9
-10
spec/javascripts/notes_spec.js
spec/javascripts/notes_spec.js
+1
-3
spec/javascripts/right_sidebar_spec.js
spec/javascripts/right_sidebar_spec.js
+1
-2
spec/serializers/merge_request_serializer_spec.rb
spec/serializers/merge_request_serializer_spec.rb
+27
-21
spec/serializers/merge_request_widget_entity_spec.rb
spec/serializers/merge_request_widget_entity_spec.rb
+1
-44
No files found.
.gitlab-ci.yml
View file @
f8cda25d
image
:
"
dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.13-chrome-62.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner
:
&dedicated-runner
retry
:
1
tags
:
-
gitlab-org
.default-cache
:
&default-cache
key
:
"
ruby-235-with-yarn"
paths
:
...
...
@@ -18,7 +23,6 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git
variables
:
MYSQL_ALLOW_EMPTY_PASSWORD
:
"
1"
ELASTIC_URL
:
"
http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
RAILS_ENV
:
"
test"
NODE_ENV
:
"
test"
SIMPLECOV
:
"
true"
...
...
@@ -28,8 +32,10 @@ variables:
KNAPSACK_RSPEC_SUITE_REPORT_PATH
:
knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
KNAPSACK_SPINACH_SUITE_REPORT_PATH
:
knapsack/${CI_PROJECT_NAME}/spinach_report-master.json
FLAKY_RSPEC_SUITE_REPORT_PATH
:
rspec_flaky/report-suite.json
## EE specific variables ##
# This hack is needed to make ES not that memory hungry
ES_JAVA_OPTS
:
"
-Xms256m
-Xmx256m"
ELASTIC_URL
:
"
http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
before_script
:
-
bundle --version
...
...
@@ -45,11 +51,6 @@ stages:
-
post-cleanup
# Predefined scopes
.dedicated-runner
:
&dedicated-runner
retry
:
1
tags
:
-
gitlab-org
.tests-metadata-state
:
&tests-metadata-state
<<
:
*dedicated-runner
variables
:
...
...
@@ -89,8 +90,8 @@ stages:
.rspec-metadata
:
&rspec-metadata
<<
:
*dedicated-runner
<<
:
*pull-cache
<<
:
*except-docs
<<
:
*pull-cache
stage
:
test
script
:
-
JOB_NAME=( $CI_JOB_NAME )
...
...
@@ -138,8 +139,8 @@ stages:
.spinach-metadata
:
&spinach-metadata
<<
:
*dedicated-runner
<<
:
*pull-cache
<<
:
*except-docs
<<
:
*pull-cache
stage
:
test
script
:
-
JOB_NAME=( $CI_JOB_NAME )
...
...
@@ -178,6 +179,7 @@ stages:
# Trigger a package build in omnibus-gitlab repository
#
package-qa
:
<<
:
*dedicated-runner
image
:
ruby:2.4-alpine
before_script
:
[]
stage
:
build
...
...
@@ -191,6 +193,7 @@ package-qa:
# Review docs base
.review-docs
:
&review-docs
<<
:
*dedicated-runner
image
:
ruby:2.4-alpine
before_script
:
-
gem install gitlab --no-doc
...
...
@@ -297,9 +300,9 @@ flaky-examples-check:
-
scripts/detect-new-flaky-examples $NEW_FLAKY_SPECS_REPORT
setup-test-env
:
<<
:
*use-pg
<<
:
*dedicated-runner
<<
:
*except-docs
<<
:
*use-pg
stage
:
prepare
cache
:
<<
:
*default-cache
...
...
@@ -390,18 +393,18 @@ spinach-mysql 3 4: *spinach-metadata-mysql
SETUP_DB
:
"
false"
.rake-exec
:
&rake-exec
<<
:
*ruby-static-analysis
<<
:
*dedicated-runner
<<
:
*except-docs
<<
:
*pull-cache
<<
:
*ruby-static-analysis
stage
:
test
script
:
-
bundle exec rake $CI_JOB_NAME
static-analysis
:
<<
:
*ruby-static-analysis
<<
:
*dedicated-runner
<<
:
*except-docs
<<
:
*ruby-static-analysis
stage
:
test
script
:
-
scripts/static-analysis
...
...
@@ -467,10 +470,16 @@ db:migrate:reset-mysql:
<<
:
*db-migrate-reset
<<
:
*use-mysql
db:check-schema-pg:
<<
:
*db-migrate-reset
<<
:
*use-pg
script
:
-
source scripts/schema_changed.sh
.migration-paths
:
&migration-paths
<<
:
*dedicated-runner
<<
:
*pull-cache
<<
:
*except-docs
<<
:
*pull-cache
stage
:
test
variables
:
SETUP_DB
:
"
false"
...
...
@@ -538,12 +547,6 @@ db:seed_fu-mysql:
<<
:
*db-seed_fu
<<
:
*use-mysql
db:check-schema-pg:
<<
:
*db-migrate-reset
<<
:
*use-pg
script
:
-
sh scripts/schema_changed.sh
# Frontend-related jobs
gitlab:assets:compile:
<<
:
*dedicated-runner
...
...
@@ -568,10 +571,10 @@ gitlab:assets:compile:
-
webpack-report/
karma
:
<<
:
*use-pg
<<
:
*dedicated-runner
<<
:
*except-docs
<<
:
*pull-cache
<<
:
*use-pg
stage
:
test
variables
:
BABEL_ENV
:
"
coverage"
...
...
@@ -610,6 +613,7 @@ codequality:
paths
:
[
codeclimate.json
]
qa:internal:
<<
:
*dedicated-runner
<<
:
*except-docs
stage
:
test
variables
:
...
...
@@ -699,8 +703,9 @@ cache gems:
-
master@gitlab-org/gitlab-ee
gitlab_git_test
:
<<
:
*
pull-cache
<<
:
*
dedicated-runner
<<
:
*except-docs
<<
:
*pull-cache
variables
:
SETUP_DB
:
"
false"
script
:
...
...
app/assets/javascripts/boards/components/board_sidebar.js
View file @
f8cda25d
/* eslint-disable comma-dangle, space-before-function-paren, no-new */
/* global MilestoneSelect */
/* global Sidebar */
import
Vue
from
'
vue
'
;
import
weight
from
'
ee/sidebar/components/weight/weight.vue
'
;
import
Flash
from
'
../../flash
'
;
import
Sidebar
from
'
../../right_sidebar
'
;
import
eventHub
from
'
../../sidebar/event_hub
'
;
import
assigneeTitle
from
'
../../sidebar/components/assignees/assignee_title
'
;
import
assignees
from
'
../../sidebar/components/assignees/assignees
'
;
...
...
app/assets/javascripts/boards/models/issue.js
View file @
f8cda25d
...
...
@@ -25,7 +25,6 @@ class ListIssue {
this
.
isLoading
=
{
weight
:
false
,
};
this
.
isLoading
=
{};
this
.
sidebarInfoEndpoint
=
obj
.
issue_sidebar_endpoint
;
this
.
toggleSubscriptionEndpoint
=
obj
.
toggle_subscription_endpoint
;
this
.
milestone_id
=
obj
.
milestone_id
;
...
...
app/assets/javascripts/diff_notes/components/diff_note_avatars.js
View file @
f8cda25d
/* global CommentsStore */
/* global notes */
import
Vue
from
'
vue
'
;
import
collapseIcon
from
'
../icons/collapse_icon.svg
'
;
import
Notes
from
'
../../notes
'
;
import
userAvatarImage
from
'
../../vue_shared/components/user_avatar/user_avatar_image.vue
'
;
const
DiffNoteAvatars
=
Vue
.
extend
({
...
...
@@ -129,7 +129,7 @@ const DiffNoteAvatars = Vue.extend({
},
methods
:
{
clickedAvatar
(
e
)
{
notes
.
onAddDiffNote
(
e
);
Notes
.
instance
.
onAddDiffNote
(
e
);
// Toggle the active state of the toggle all button
this
.
toggleDiscussionsToggleState
();
...
...
app/assets/javascripts/dispatcher.js
View file @
f8cda25d
...
...
@@ -11,7 +11,7 @@ import NewBranchForm from './new_branch_form';
/* global NotificationsDropdown */
import
groupAvatar
from
'
./group_avatar
'
;
import
GroupLabelSubscription
from
'
./group_label_subscription
'
;
/* global LineHighlighter */
import
LineHighlighter
from
'
./line_highlighter
'
;
import
BuildArtifacts
from
'
./build_artifacts
'
;
import
CILintEditor
from
'
./ci_lint_editor
'
;
import
groupsSelect
from
'
./groups_select
'
;
...
...
@@ -21,7 +21,7 @@ import NamespaceSelect from './namespace_select';
import
NewCommitForm
from
'
./new_commit_form
'
;
import
Project
from
'
./project
'
;
import
projectAvatar
from
'
./project_avatar
'
;
/* global MergeRequest */
import
MergeRequest
from
'
./merge_request
'
;
import
Compare
from
'
./compare
'
;
import
initCompareAutocomplete
from
'
./compare_autocomplete
'
;
/* global PathLocks */
...
...
@@ -30,7 +30,7 @@ import ProjectNew from './project_new';
import
projectImport
from
'
./project_import
'
;
import
Labels
from
'
./labels
'
;
import
LabelManager
from
'
./label_manager
'
;
/* global Sidebar */
import
Sidebar
from
'
./right_sidebar
'
;
/* global WeightSelect */
/* global AdminEmailSelect */
...
...
app/assets/javascripts/geo_nodes.js
View file @
f8cda25d
...
...
@@ -104,8 +104,8 @@ class GeoNodeStatus {
graphItems
.
forEach
((
item
)
=>
{
$itemEl
.
find
(
item
.
itemSel
)
.
toggleClass
(
'
has-value has-tooltip
'
,
!!
item
.
itemCount
)
.
attr
(
'
data-original-title
'
,
`
${
item
.
itemTooltip
}
:
${
item
.
item
Percent
}
%
`
)
.
text
(
item
.
itemCount
||
''
)
.
attr
(
'
data-original-title
'
,
`
${
item
.
itemTooltip
}
:
${
item
.
item
Count
}
`
)
.
text
(
`
${
item
.
itemPercent
}
%`
||
''
)
.
css
(
'
width
'
,
`
${
item
.
itemPercent
}
%`
);
});
}
...
...
app/assets/javascripts/init_issuable_sidebar.js
View file @
f8cda25d
...
...
@@ -3,7 +3,7 @@
/* global WeightSelect */
import
LabelsSelect
from
'
./labels_select
'
;
import
IssuableContext
from
'
./issuable_context
'
;
/* global Sidebar */
import
Sidebar
from
'
./right_sidebar
'
;
import
DueDateSelectors
from
'
./due_date_select
'
;
...
...
@@ -17,5 +17,5 @@ export default () => {
new
WeightSelect
();
new
IssuableContext
(
sidebarOptions
.
currentUser
);
new
DueDateSelectors
();
window
.
sidebar
=
new
Sidebar
();
Sidebar
.
initialize
();
};
app/assets/javascripts/init_notes.js
View file @
f8cda25d
/* global Notes */
import
Notes
from
'
./notes
'
;
export
default
()
=>
{
const
dataEl
=
document
.
querySelector
(
'
.js-notes-data
'
);
...
...
@@ -10,5 +10,7 @@ export default () => {
autocomplete
,
}
=
JSON
.
parse
(
dataEl
.
innerHTML
);
window
.
notes
=
new
Notes
(
notesUrl
,
notesIds
,
now
,
diffView
,
autocomplete
);
// Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance
Notes
.
initialize
(
notesUrl
,
notesIds
,
now
,
diffView
,
autocomplete
);
};
app/assets/javascripts/line_highlighter.js
View file @
f8cda25d
...
...
@@ -175,4 +175,4 @@ LineHighlighter.prototype.__setLocationHash__ = function(value) {
},
document
.
title
,
value
);
};
window
.
LineHighlighter
=
LineHighlighter
;
export
default
LineHighlighter
;
app/assets/javascripts/main.js
View file @
f8cda25d
...
...
@@ -50,10 +50,7 @@ import './layout_nav';
import
LazyLoader
from
'
./lazy_loader
'
;
import
'
./line_highlighter
'
;
import
initLogoAnimation
from
'
./logo
'
;
import
'
./merge_request
'
;
import
'
./merge_request_tabs
'
;
import
'
./milestone_select
'
;
import
'
./notes
'
;
import
'
./notifications_dropdown
'
;
import
'
./notifications_form
'
;
import
'
./pager
'
;
...
...
@@ -61,7 +58,6 @@ import './preview_markdown';
import
'
./project_import
'
;
import
'
./projects_dropdown
'
;
import
'
./render_gfm
'
;
import
'
./right_sidebar
'
;
import
initBreadcrumbs
from
'
./breadcrumb
'
;
// EE-only scripts
...
...
app/assets/javascripts/merge_request.js
View file @
f8cda25d
...
...
@@ -7,142 +7,138 @@ import './merge_request_tabs';
import
IssuablesHelper
from
'
./helpers/issuables_helper
'
;
import
{
addDelimiter
}
from
'
./lib/utils/text_utility
'
;
(
function
()
{
this
.
MergeRequest
=
(
function
()
{
function
MergeRequest
(
opts
)
{
// Initialize MergeRequest behavior
//
// Options:
// action - String, current controller action
//
this
.
opts
=
opts
!=
null
?
opts
:
{};
this
.
submitNoteForm
=
this
.
submitNoteForm
.
bind
(
this
);
this
.
$el
=
$
(
'
.merge-request
'
);
this
.
$
(
'
.show-all-commits
'
).
on
(
'
click
'
,
(
function
(
_this
)
{
return
function
()
{
return
_this
.
showAllCommits
();
};
})(
this
));
this
.
initTabs
();
this
.
initMRBtnListeners
();
this
.
initCommitMessageListeners
();
this
.
closeReopenReportToggle
=
IssuablesHelper
.
initCloseReopenReport
();
if
(
$
(
"
a.btn-close
"
).
length
)
{
this
.
taskList
=
new
TaskList
({
dataType
:
'
merge_request
'
,
fieldName
:
'
description
'
,
selector
:
'
.detail-page-description
'
,
onSuccess
:
(
result
)
=>
{
document
.
querySelector
(
'
#task_status
'
).
innerText
=
result
.
task_status
;
document
.
querySelector
(
'
#task_status_short
'
).
innerText
=
result
.
task_status_short
;
}
});
}
}
// Local jQuery finder
MergeRequest
.
prototype
.
$
=
function
(
selector
)
{
return
this
.
$el
.
find
(
selector
);
function
MergeRequest
(
opts
)
{
// Initialize MergeRequest behavior
//
// Options:
// action - String, current controller action
//
this
.
opts
=
opts
!=
null
?
opts
:
{};
this
.
submitNoteForm
=
this
.
submitNoteForm
.
bind
(
this
);
this
.
$el
=
$
(
'
.merge-request
'
);
this
.
$
(
'
.show-all-commits
'
).
on
(
'
click
'
,
(
function
(
_this
)
{
return
function
()
{
return
_this
.
showAllCommits
();
};
MergeRequest
.
prototype
.
initTabs
=
function
()
{
if
(
window
.
mrTabs
)
{
window
.
mrTabs
.
unbindEvents
();
})(
this
));
this
.
initTabs
();
this
.
initMRBtnListeners
();
this
.
initCommitMessageListeners
();
this
.
closeReopenReportToggle
=
IssuablesHelper
.
initCloseReopenReport
();
if
(
$
(
"
a.btn-close
"
).
length
)
{
this
.
taskList
=
new
TaskList
({
dataType
:
'
merge_request
'
,
fieldName
:
'
description
'
,
selector
:
'
.detail-page-description
'
,
onSuccess
:
(
result
)
=>
{
document
.
querySelector
(
'
#task_status
'
).
innerText
=
result
.
task_status
;
document
.
querySelector
(
'
#task_status_short
'
).
innerText
=
result
.
task_status_short
;
}
window
.
mrTabs
=
new
gl
.
MergeRequestTabs
(
this
.
opts
);
};
MergeRequest
.
prototype
.
showAllCommits
=
function
()
{
this
.
$
(
'
.first-commits
'
).
remove
();
return
this
.
$
(
'
.all-commits
'
).
removeClass
(
'
hide
'
);
};
MergeRequest
.
prototype
.
initMRBtnListeners
=
function
()
{
var
_this
;
_this
=
this
;
return
$
(
'
a.btn-close, a.btn-reopen
'
).
on
(
'
click
'
,
function
(
e
)
{
var
$this
,
shouldSubmit
;
$this
=
$
(
this
);
shouldSubmit
=
$this
.
hasClass
(
'
btn-comment
'
);
if
(
shouldSubmit
&&
$this
.
data
(
'
submitted
'
))
{
return
;
}
if
(
this
.
closeReopenReportToggle
)
this
.
closeReopenReportToggle
.
setDisable
();
if
(
shouldSubmit
)
{
if
(
$this
.
hasClass
(
'
btn-comment-and-close
'
)
||
$this
.
hasClass
(
'
btn-comment-and-reopen
'
))
{
e
.
preventDefault
();
e
.
stopImmediatePropagation
();
_this
.
submitNoteForm
(
$this
.
closest
(
'
form
'
),
$this
);
}
}
});
};
MergeRequest
.
prototype
.
submitNoteForm
=
function
(
form
,
$button
)
{
var
noteText
;
noteText
=
form
.
find
(
"
textarea.js-note-text
"
).
val
();
if
(
noteText
.
trim
().
length
>
0
)
{
form
.
submit
();
$button
.
data
(
'
submitted
'
,
true
);
return
$button
.
trigger
(
'
click
'
);
}
};
MergeRequest
.
prototype
.
initCommitMessageListeners
=
function
()
{
$
(
document
).
on
(
'
click
'
,
'
a.js-with-description-link
'
,
function
(
e
)
{
var
textarea
=
$
(
'
textarea.js-commit-message
'
);
e
.
preventDefault
();
});
}
}
// Local jQuery finder
MergeRequest
.
prototype
.
$
=
function
(
selector
)
{
return
this
.
$el
.
find
(
selector
);
};
MergeRequest
.
prototype
.
initTabs
=
function
()
{
if
(
window
.
mrTabs
)
{
window
.
mrTabs
.
unbindEvents
();
}
window
.
mrTabs
=
new
gl
.
MergeRequestTabs
(
this
.
opts
);
};
MergeRequest
.
prototype
.
showAllCommits
=
function
()
{
this
.
$
(
'
.first-commits
'
).
remove
();
return
this
.
$
(
'
.all-commits
'
).
removeClass
(
'
hide
'
);
};
MergeRequest
.
prototype
.
initMRBtnListeners
=
function
()
{
var
_this
;
_this
=
this
;
return
$
(
'
a.btn-close, a.btn-reopen
'
).
on
(
'
click
'
,
function
(
e
)
{
var
$this
,
shouldSubmit
;
$this
=
$
(
this
);
shouldSubmit
=
$this
.
hasClass
(
'
btn-comment
'
);
if
(
shouldSubmit
&&
$this
.
data
(
'
submitted
'
))
{
return
;
}
textarea
.
val
(
textarea
.
data
(
'
messageWithDescription
'
));
$
(
'
.js-with-description-hint
'
).
hide
();
$
(
'
.js-without-description-hint
'
).
show
();
});
if
(
this
.
closeReopenReportToggle
)
this
.
closeReopenReportToggle
.
setDisable
();
$
(
document
).
on
(
'
click
'
,
'
a.js-without-description-link
'
,
function
(
e
)
{
var
textarea
=
$
(
'
textarea.js-commit-message
'
);
if
(
shouldSubmit
)
{
if
(
$this
.
hasClass
(
'
btn-comment-and-close
'
)
||
$this
.
hasClass
(
'
btn-comment-and-reopen
'
))
{
e
.
preventDefault
();
e
.
stopImmediatePropagation
();
textarea
.
val
(
textarea
.
data
(
'
messageWithoutDescription
'
));
$
(
'
.js-with-description-hint
'
).
show
();
$
(
'
.js-without-description-hint
'
).
hide
();
});
};
MergeRequest
.
prototype
.
updateStatusText
=
function
(
classToRemove
,
classToAdd
,
newStatusText
)
{
$
(
'
.detail-page-header .status-box
'
)
.
removeClass
(
classToRemove
)
.
addClass
(
classToAdd
)
.
find
(
'
span
'
)
.
text
(
newStatusText
);
};
MergeRequest
.
prototype
.
decreaseCounter
=
function
(
by
=
1
)
{
const
$el
=
$
(
'
.nav-links .js-merge-counter
'
);
const
count
=
Math
.
max
((
parseInt
(
$el
.
text
().
replace
(
/
[^\d]
/
,
''
),
10
)
-
by
),
0
);
$el
.
text
(
addDelimiter
(
count
));
};
MergeRequest
.
prototype
.
hideCloseButton
=
function
()
{
const
el
=
document
.
querySelector
(
'
.merge-request .js-issuable-actions
'
);
const
closeDropdownItem
=
el
.
querySelector
(
'
li.close-item
'
);
if
(
closeDropdownItem
)
{
closeDropdownItem
.
classList
.
add
(
'
hidden
'
);
// Selects the next dropdown item
el
.
querySelector
(
'
li.report-item
'
).
click
();
}
else
{
// No dropdown just hide the Close button
el
.
querySelector
(
'
.btn-close
'
).
classList
.
add
(
'
hidden
'
);
_this
.
submitNoteForm
(
$this
.
closest
(
'
form
'
),
$this
);
}
// Dropdown for mobile screen
el
.
querySelector
(
'
li.js-close-item
'
).
classList
.
add
(
'
hidden
'
);
};
return
MergeRequest
;
})();
}).
call
(
window
);
}
});
};
MergeRequest
.
prototype
.
submitNoteForm
=
function
(
form
,
$button
)
{
var
noteText
;
noteText
=
form
.
find
(
"
textarea.js-note-text
"
).
val
();
if
(
noteText
.
trim
().
length
>
0
)
{
form
.
submit
();
$button
.
data
(
'
submitted
'
,
true
);
return
$button
.
trigger
(
'
click
'
);
}
};
MergeRequest
.
prototype
.
initCommitMessageListeners
=
function
()
{
$
(
document
).
on
(
'
click
'
,
'
a.js-with-description-link
'
,
function
(
e
)
{
var
textarea
=
$
(
'
textarea.js-commit-message
'
);
e
.
preventDefault
();
textarea
.
val
(
textarea
.
data
(
'
messageWithDescription
'
));
$
(
'
.js-with-description-hint
'
).
hide
();
$
(
'
.js-without-description-hint
'
).
show
();
});
$
(
document
).
on
(
'
click
'
,
'
a.js-without-description-link
'
,
function
(
e
)
{
var
textarea
=
$
(
'
textarea.js-commit-message
'
);
e
.
preventDefault
();
textarea
.
val
(
textarea
.
data
(
'
messageWithoutDescription
'
));
$
(
'
.js-with-description-hint
'
).
show
();
$
(
'
.js-without-description-hint
'
).
hide
();
});
};
MergeRequest
.
prototype
.
updateStatusText
=
function
(
classToRemove
,
classToAdd
,
newStatusText
)
{
$
(
'
.detail-page-header .status-box
'
)
.
removeClass
(
classToRemove
)
.
addClass
(
classToAdd
)
.
find
(
'
span
'
)
.
text
(
newStatusText
);
};
MergeRequest
.
prototype
.
decreaseCounter
=
function
(
by
=
1
)
{
const
$el
=
$
(
'
.nav-links .js-merge-counter
'
);
const
count
=
Math
.
max
((
parseInt
(
$el
.
text
().
replace
(
/
[^\d]
/
,
''
),
10
)
-
by
),
0
);
$el
.
text
(
addDelimiter
(
count
));
};
MergeRequest
.
prototype
.
hideCloseButton
=
function
()
{
const
el
=
document
.
querySelector
(
'
.merge-request .js-issuable-actions
'
);
const
closeDropdownItem
=
el
.
querySelector
(
'
li.close-item
'
);
if
(
closeDropdownItem
)
{
closeDropdownItem
.
classList
.
add
(
'
hidden
'
);
// Selects the next dropdown item
el
.
querySelector
(
'
li.report-item
'
).
click
();
}
else
{
// No dropdown just hide the Close button
el
.
querySelector
(
'
.btn-close
'
).
classList
.
add
(
'
hidden
'
);
}
// Dropdown for mobile screen
el
.
querySelector
(
'
li.js-close-item
'
).
classList
.
add
(
'
hidden
'
);
};
export
default
MergeRequest
;
app/assets/javascripts/merge_request_tabs.js
View file @
f8cda25d
/* eslint-disable no-new, class-methods-use-this */
/* global notes */
import
Cookies
from
'
js-cookie
'
;
import
Flash
from
'
./flash
'
;
...
...
@@ -16,6 +15,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
import
Diff
from
'
./diff
'
;
import
{
localTimeAgo
}
from
'
./lib/utils/datetime_utility
'
;
import
syntaxHighlight
from
'
./syntax_highlight
'
;
import
Notes
from
'
./notes
'
;
/* eslint-disable max-len */
// MergeRequestTabs
...
...
@@ -325,7 +325,7 @@ import syntaxHighlight from './syntax_highlight';
if
(
anchor
&&
anchor
.
length
>
0
)
{
const
notesContent
=
anchor
.
closest
(
'
.notes_content
'
);
const
lineType
=
notesContent
.
hasClass
(
'
new
'
)
?
'
new
'
:
'
old
'
;
notes
.
toggleDiffNote
({
Notes
.
instance
.
toggleDiffNote
({
target
:
anchor
,
lineType
,
forceShow
:
true
,
...
...
app/assets/javascripts/notes.js
View file @
f8cda25d
...
...
@@ -37,6 +37,12 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const
REGEX_QUICK_ACTIONS
=
/^
\/\w
+.*$/gm
;
export
default
class
Notes
{
static
initialize
(
notes_url
,
note_ids
,
last_fetched_at
,
view
,
enableGFM
=
true
)
{
if
(
!
this
.
instance
)
{
this
.
instance
=
new
Notes
(
notes_url
,
note_ids
,
last_fetched_at
,
view
,
enableGFM
);
}
}
constructor
(
notes_url
,
note_ids
,
last_fetched_at
,
view
,
enableGFM
=
true
)
{
this
.
updateTargetButtons
=
this
.
updateTargetButtons
.
bind
(
this
);
this
.
updateComment
=
this
.
updateComment
.
bind
(
this
);
...
...
app/assets/javascripts/repo/components/repo_preview.vue
View file @
f8cda25d
<
script
>
/* global LineHighlighter */
import
{
mapGetters
}
from
'
vuex
'
;
import
LineHighlighter
from
'
../../line_highlighter
'
;
import
syntaxHighlight
from
'
../../syntax_highlight
'
;
export
default
{
...
...
app/assets/javascripts/right_sidebar.js
View file @
f8cda25d
...
...
@@ -3,226 +3,228 @@
import
_
from
'
underscore
'
;
import
Cookies
from
'
js-cookie
'
;
(
function
()
{
this
.
Sidebar
=
(
function
()
{
function
Sidebar
(
currentUser
)
{
this
.
toggleTodo
=
this
.
toggleTodo
.
bind
(
this
);
this
.
sidebar
=
$
(
'
aside
'
);
this
.
removeListeners
();
this
.
addEventListeners
();
function
Sidebar
(
currentUser
)
{
this
.
toggleTodo
=
this
.
toggleTodo
.
bind
(
this
);
this
.
sidebar
=
$
(
'
aside
'
);
this
.
removeListeners
();
this
.
addEventListeners
();
}
Sidebar
.
initialize
=
function
(
currentUser
)
{
if
(
!
this
.
instance
)
{
this
.
instance
=
new
Sidebar
(
currentUser
);
}
};
Sidebar
.
prototype
.
removeListeners
=
function
()
{
this
.
sidebar
.
off
(
'
click
'
,
'
.sidebar-collapsed-icon
'
);
this
.
sidebar
.
off
(
'
hidden.gl.dropdown
'
);
$
(
'
.dropdown
'
).
off
(
'
loading.gl.dropdown
'
);
$
(
'
.dropdown
'
).
off
(
'
loaded.gl.dropdown
'
);
$
(
document
).
off
(
'
click
'
,
'
.js-sidebar-toggle
'
);
};
Sidebar
.
prototype
.
addEventListeners
=
function
()
{
const
$document
=
$
(
document
);
this
.
sidebar
.
on
(
'
click
'
,
'
.sidebar-collapsed-icon
'
,
this
,
this
.
sidebarCollapseClicked
);
this
.
sidebar
.
on
(
'
hidden.gl.dropdown
'
,
this
,
this
.
onSidebarDropdownHidden
);
$
(
'
.dropdown
'
).
on
(
'
loading.gl.dropdown
'
,
this
.
sidebarDropdownLoading
);
$
(
'
.dropdown
'
).
on
(
'
loaded.gl.dropdown
'
,
this
.
sidebarDropdownLoaded
);
$document
.
on
(
'
click
'
,
'
.js-sidebar-toggle
'
,
this
.
sidebarToggleClicked
);
return
$
(
document
).
off
(
'
click
'
,
'
.js-issuable-todo
'
).
on
(
'
click
'
,
'
.js-issuable-todo
'
,
this
.
toggleTodo
);
};
Sidebar
.
prototype
.
sidebarToggleClicked
=
function
(
e
,
triggered
)
{
var
$allGutterToggleIcons
,
$this
,
$thisIcon
;
e
.
preventDefault
();
$this
=
$
(
this
);
$thisIcon
=
$this
.
find
(
'
i
'
);
$allGutterToggleIcons
=
$
(
'
.js-sidebar-toggle i
'
);
if
(
$thisIcon
.
hasClass
(
'
fa-angle-double-right
'
))
{
$allGutterToggleIcons
.
removeClass
(
'
fa-angle-double-right
'
).
addClass
(
'
fa-angle-double-left
'
);
$
(
'
aside.right-sidebar
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
}
else
{
$allGutterToggleIcons
.
removeClass
(
'
fa-angle-double-left
'
).
addClass
(
'
fa-angle-double-right
'
);
$
(
'
aside.right-sidebar
'
).
removeClass
(
'
right-sidebar-collapsed
'
).
addClass
(
'
right-sidebar-expanded
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
right-sidebar-collapsed
'
).
addClass
(
'
right-sidebar-expanded
'
);
if
(
gl
.
lazyLoader
)
gl
.
lazyLoader
.
loadCheck
();
}
if
(
!
triggered
)
{
Cookies
.
set
(
"
collapsed_gutter
"
,
$
(
'
.right-sidebar
'
).
hasClass
(
'
right-sidebar-collapsed
'
));
}
};
Sidebar
.
prototype
.
toggleTodo
=
function
(
e
)
{
var
$btnText
,
$this
,
$todoLoading
,
ajaxType
,
url
;
$this
=
$
(
e
.
currentTarget
);
ajaxType
=
$this
.
attr
(
'
data-delete-path
'
)
?
'
DELETE
'
:
'
POST
'
;
if
(
$this
.
attr
(
'
data-delete-path
'
))
{
url
=
""
+
(
$this
.
attr
(
'
data-delete-path
'
));
}
else
{
url
=
""
+
(
$this
.
data
(
'
url
'
));
}
$this
.
tooltip
(
'
hide
'
);
return
$
.
ajax
({
url
:
url
,
type
:
ajaxType
,
dataType
:
'
json
'
,
data
:
{
issuable_id
:
$this
.
data
(
'
issuable-id
'
),
issuable_type
:
$this
.
data
(
'
issuable-type
'
)
},
beforeSend
:
(
function
(
_this
)
{
return
function
()
{
$
(
'
.js-issuable-todo
'
).
disable
()
.
addClass
(
'
is-loading
'
);
};
})(
this
)
}).
done
((
function
(
_this
)
{
return
function
(
data
)
{
return
_this
.
todoUpdateDone
(
data
);
};
})(
this
));
};
Sidebar
.
prototype
.
todoUpdateDone
=
function
(
data
)
{
const
deletePath
=
data
.
delete_path
?
data
.
delete_path
:
null
;
const
attrPrefix
=
deletePath
?
'
mark
'
:
'
todo
'
;
const
$todoBtns
=
$
(
'
.js-issuable-todo
'
);
$
(
document
).
trigger
(
'
todo:toggle
'
,
data
.
count
);
$todoBtns
.
each
((
i
,
el
)
=>
{
const
$el
=
$
(
el
);
const
$elText
=
$el
.
find
(
'
.js-issuable-todo-inner
'
);
$el
.
removeClass
(
'
is-loading
'
)
.
enable
()
.
attr
(
'
aria-label
'
,
$el
.
data
(
`
${
attrPrefix
}
-text`
))
.
attr
(
'
data-delete-path
'
,
deletePath
)
.
attr
(
'
title
'
,
$el
.
data
(
`
${
attrPrefix
}
-text`
));
if
(
$el
.
hasClass
(
'
has-tooltip
'
))
{
$el
.
tooltip
(
'
fixTitle
'
);
}
Sidebar
.
prototype
.
removeListeners
=
function
()
{
this
.
sidebar
.
off
(
'
click
'
,
'
.sidebar-collapsed-icon
'
);
this
.
sidebar
.
off
(
'
hidden.gl.dropdown
'
);
$
(
'
.dropdown
'
).
off
(
'
loading.gl.dropdown
'
);
$
(
'
.dropdown
'
).
off
(
'
loaded.gl.dropdown
'
);
$
(
document
).
off
(
'
click
'
,
'
.js-sidebar-toggle
'
);
};
Sidebar
.
prototype
.
addEventListeners
=
function
()
{
const
$document
=
$
(
document
);
this
.
sidebar
.
on
(
'
click
'
,
'
.sidebar-collapsed-icon
'
,
this
,
this
.
sidebarCollapseClicked
);
this
.
sidebar
.
on
(
'
hidden.gl.dropdown
'
,
this
,
this
.
onSidebarDropdownHidden
);
$
(
'
.dropdown
'
).
on
(
'
loading.gl.dropdown
'
,
this
.
sidebarDropdownLoading
);
$
(
'
.dropdown
'
).
on
(
'
loaded.gl.dropdown
'
,
this
.
sidebarDropdownLoaded
);
$document
.
on
(
'
click
'
,
'
.js-sidebar-toggle
'
,
this
.
sidebarToggleClicked
);
return
$
(
document
).
off
(
'
click
'
,
'
.js-issuable-todo
'
).
on
(
'
click
'
,
'
.js-issuable-todo
'
,
this
.
toggleTodo
);
};
Sidebar
.
prototype
.
sidebarToggleClicked
=
function
(
e
,
triggered
)
{
var
$allGutterToggleIcons
,
$this
,
$thisIcon
;
e
.
preventDefault
();
$this
=
$
(
this
);
$thisIcon
=
$this
.
find
(
'
i
'
);
$allGutterToggleIcons
=
$
(
'
.js-sidebar-toggle i
'
);
if
(
$thisIcon
.
hasClass
(
'
fa-angle-double-right
'
))
{
$allGutterToggleIcons
.
removeClass
(
'
fa-angle-double-right
'
).
addClass
(
'
fa-angle-double-left
'
);
$
(
'
aside.right-sidebar
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
}
else
{
$allGutterToggleIcons
.
removeClass
(
'
fa-angle-double-left
'
).
addClass
(
'
fa-angle-double-right
'
);
$
(
'
aside.right-sidebar
'
).
removeClass
(
'
right-sidebar-collapsed
'
).
addClass
(
'
right-sidebar-expanded
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
right-sidebar-collapsed
'
).
addClass
(
'
right-sidebar-expanded
'
);
if
(
gl
.
lazyLoader
)
gl
.
lazyLoader
.
loadCheck
();
}
if
(
!
triggered
)
{
Cookies
.
set
(
"
collapsed_gutter
"
,
$
(
'
.right-sidebar
'
).
hasClass
(
'
right-sidebar-collapsed
'
));
}
};
Sidebar
.
prototype
.
toggleTodo
=
function
(
e
)
{
var
$btnText
,
$this
,
$todoLoading
,
ajaxType
,
url
;
$this
=
$
(
e
.
currentTarget
);
ajaxType
=
$this
.
attr
(
'
data-delete-path
'
)
?
'
DELETE
'
:
'
POST
'
;
if
(
$this
.
attr
(
'
data-delete-path
'
))
{
url
=
""
+
(
$this
.
attr
(
'
data-delete-path
'
));
}
else
{
url
=
""
+
(
$this
.
data
(
'
url
'
));
}
$this
.
tooltip
(
'
hide
'
);
return
$
.
ajax
({
url
:
url
,
type
:
ajaxType
,
dataType
:
'
json
'
,
data
:
{
issuable_id
:
$this
.
data
(
'
issuable-id
'
),
issuable_type
:
$this
.
data
(
'
issuable-type
'
)
},
beforeSend
:
(
function
(
_this
)
{
return
function
()
{
$
(
'
.js-issuable-todo
'
).
disable
()
.
addClass
(
'
is-loading
'
);
};
})(
this
)
}).
done
((
function
(
_this
)
{
return
function
(
data
)
{
return
_this
.
todoUpdateDone
(
data
);
};
})(
this
));
};
Sidebar
.
prototype
.
todoUpdateDone
=
function
(
data
)
{
const
deletePath
=
data
.
delete_path
?
data
.
delete_path
:
null
;
const
attrPrefix
=
deletePath
?
'
mark
'
:
'
todo
'
;
const
$todoBtns
=
$
(
'
.js-issuable-todo
'
);
$
(
document
).
trigger
(
'
todo:toggle
'
,
data
.
count
);
$todoBtns
.
each
((
i
,
el
)
=>
{
const
$el
=
$
(
el
);
const
$elText
=
$el
.
find
(
'
.js-issuable-todo-inner
'
);
$el
.
removeClass
(
'
is-loading
'
)
.
enable
()
.
attr
(
'
aria-label
'
,
$el
.
data
(
`
${
attrPrefix
}
-text`
))
.
attr
(
'
data-delete-path
'
,
deletePath
)
.
attr
(
'
title
'
,
$el
.
data
(
`
${
attrPrefix
}
-text`
));
if
(
$el
.
hasClass
(
'
has-tooltip
'
))
{
$el
.
tooltip
(
'
fixTitle
'
);
}
if
(
$el
.
data
(
`
${
attrPrefix
}
-icon`
))
{
$elText
.
html
(
$el
.
data
(
`
${
attrPrefix
}
-icon`
));
}
else
{
$elText
.
text
(
$el
.
data
(
`
${
attrPrefix
}
-text`
));
}
});
};
Sidebar
.
prototype
.
sidebarDropdownLoading
=
function
(
e
)
{
var
$loading
,
$sidebarCollapsedIcon
,
i
,
img
;
$sidebarCollapsedIcon
=
$
(
this
).
closest
(
'
.block
'
).
find
(
'
.sidebar-collapsed-icon
'
);
img
=
$sidebarCollapsedIcon
.
find
(
'
img
'
);
i
=
$sidebarCollapsedIcon
.
find
(
'
i
'
);
$loading
=
$
(
'
<i class="fa fa-spinner fa-spin"></i>
'
);
if
(
img
.
length
)
{
img
.
before
(
$loading
);
return
img
.
hide
();
}
else
if
(
i
.
length
)
{
i
.
before
(
$loading
);
return
i
.
hide
();
}
};
Sidebar
.
prototype
.
sidebarDropdownLoaded
=
function
(
e
)
{
var
$sidebarCollapsedIcon
,
i
,
img
;
$sidebarCollapsedIcon
=
$
(
this
).
closest
(
'
.block
'
).
find
(
'
.sidebar-collapsed-icon
'
);
img
=
$sidebarCollapsedIcon
.
find
(
'
img
'
);
$sidebarCollapsedIcon
.
find
(
'
i.fa-spin
'
).
remove
();
i
=
$sidebarCollapsedIcon
.
find
(
'
i
'
);
if
(
img
.
length
)
{
return
img
.
show
();
}
else
{
return
i
.
show
();
}
};
Sidebar
.
prototype
.
sidebarCollapseClicked
=
function
(
e
)
{
var
$block
,
sidebar
;
if
(
$
(
e
.
currentTarget
).
hasClass
(
'
dont-change-state
'
))
{
return
;
}
sidebar
=
e
.
data
;
e
.
preventDefault
();
$block
=
$
(
this
).
closest
(
'
.block
'
);
return
sidebar
.
openDropdown
(
$block
);
};
Sidebar
.
prototype
.
openDropdown
=
function
(
blockOrName
)
{
var
$block
;
$block
=
_
.
isString
(
blockOrName
)
?
this
.
getBlock
(
blockOrName
)
:
blockOrName
;
if
(
!
this
.
isOpen
())
{
this
.
setCollapseAfterUpdate
(
$block
);
this
.
toggleSidebar
(
'
open
'
);
}
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout
(()
=>
{
$block
.
find
(
'
.js-sidebar-dropdown-toggle
'
).
trigger
(
'
click
'
);
});
};
Sidebar
.
prototype
.
setCollapseAfterUpdate
=
function
(
$block
)
{
$block
.
addClass
(
'
collapse-after-update
'
);
return
$
(
'
.layout-page
'
).
addClass
(
'
with-overlay
'
);
};
Sidebar
.
prototype
.
onSidebarDropdownHidden
=
function
(
e
)
{
var
$block
,
sidebar
;
sidebar
=
e
.
data
;
e
.
preventDefault
();
$block
=
$
(
e
.
target
).
closest
(
'
.block
'
);
return
sidebar
.
sidebarDropdownHidden
(
$block
);
};
Sidebar
.
prototype
.
sidebarDropdownHidden
=
function
(
$block
)
{
if
(
$block
.
hasClass
(
'
collapse-after-update
'
))
{
$block
.
removeClass
(
'
collapse-after-update
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
with-overlay
'
);
return
this
.
toggleSidebar
(
'
hide
'
);
}
};
Sidebar
.
prototype
.
triggerOpenSidebar
=
function
()
{
return
this
.
sidebar
.
find
(
'
.js-sidebar-toggle
'
).
trigger
(
'
click
'
);
};
Sidebar
.
prototype
.
toggleSidebar
=
function
(
action
)
{
if
(
action
==
null
)
{
action
=
'
toggle
'
;
}
if
(
action
===
'
toggle
'
)
{
this
.
triggerOpenSidebar
();
}
if
(
action
===
'
open
'
)
{
if
(
!
this
.
isOpen
())
{
this
.
triggerOpenSidebar
();
}
}
if
(
action
===
'
hide
'
)
{
if
(
this
.
isOpen
())
{
return
this
.
triggerOpenSidebar
();
}
}
};
if
(
$el
.
data
(
`
${
attrPrefix
}
-icon`
))
{
$elText
.
html
(
$el
.
data
(
`
${
attrPrefix
}
-icon`
));
}
else
{
$elText
.
text
(
$el
.
data
(
`
${
attrPrefix
}
-text`
));
}
});
};
Sidebar
.
prototype
.
sidebarDropdownLoading
=
function
(
e
)
{
var
$loading
,
$sidebarCollapsedIcon
,
i
,
img
;
$sidebarCollapsedIcon
=
$
(
this
).
closest
(
'
.block
'
).
find
(
'
.sidebar-collapsed-icon
'
);
img
=
$sidebarCollapsedIcon
.
find
(
'
img
'
);
i
=
$sidebarCollapsedIcon
.
find
(
'
i
'
);
$loading
=
$
(
'
<i class="fa fa-spinner fa-spin"></i>
'
);
if
(
img
.
length
)
{
img
.
before
(
$loading
);
return
img
.
hide
();
}
else
if
(
i
.
length
)
{
i
.
before
(
$loading
);
return
i
.
hide
();
}
};
Sidebar
.
prototype
.
sidebarDropdownLoaded
=
function
(
e
)
{
var
$sidebarCollapsedIcon
,
i
,
img
;
$sidebarCollapsedIcon
=
$
(
this
).
closest
(
'
.block
'
).
find
(
'
.sidebar-collapsed-icon
'
);
img
=
$sidebarCollapsedIcon
.
find
(
'
img
'
);
$sidebarCollapsedIcon
.
find
(
'
i.fa-spin
'
).
remove
();
i
=
$sidebarCollapsedIcon
.
find
(
'
i
'
);
if
(
img
.
length
)
{
return
img
.
show
();
}
else
{
return
i
.
show
();
}
};
Sidebar
.
prototype
.
sidebarCollapseClicked
=
function
(
e
)
{
var
$block
,
sidebar
;
if
(
$
(
e
.
currentTarget
).
hasClass
(
'
dont-change-state
'
))
{
return
;
}
sidebar
=
e
.
data
;
e
.
preventDefault
();
$block
=
$
(
this
).
closest
(
'
.block
'
);
return
sidebar
.
openDropdown
(
$block
);
};
Sidebar
.
prototype
.
openDropdown
=
function
(
blockOrName
)
{
var
$block
;
$block
=
_
.
isString
(
blockOrName
)
?
this
.
getBlock
(
blockOrName
)
:
blockOrName
;
if
(
!
this
.
isOpen
())
{
this
.
setCollapseAfterUpdate
(
$block
);
this
.
toggleSidebar
(
'
open
'
);
}
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout
(()
=>
{
$block
.
find
(
'
.js-sidebar-dropdown-toggle
'
).
trigger
(
'
click
'
);
});
};
Sidebar
.
prototype
.
setCollapseAfterUpdate
=
function
(
$block
)
{
$block
.
addClass
(
'
collapse-after-update
'
);
return
$
(
'
.layout-page
'
).
addClass
(
'
with-overlay
'
);
};
Sidebar
.
prototype
.
onSidebarDropdownHidden
=
function
(
e
)
{
var
$block
,
sidebar
;
sidebar
=
e
.
data
;
e
.
preventDefault
();
$block
=
$
(
e
.
target
).
closest
(
'
.block
'
);
return
sidebar
.
sidebarDropdownHidden
(
$block
);
};
Sidebar
.
prototype
.
sidebarDropdownHidden
=
function
(
$block
)
{
if
(
$block
.
hasClass
(
'
collapse-after-update
'
))
{
$block
.
removeClass
(
'
collapse-after-update
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
with-overlay
'
);
return
this
.
toggleSidebar
(
'
hide
'
);
}
};
Sidebar
.
prototype
.
triggerOpenSidebar
=
function
()
{
return
this
.
sidebar
.
find
(
'
.js-sidebar-toggle
'
).
trigger
(
'
click
'
);
};
Sidebar
.
prototype
.
toggleSidebar
=
function
(
action
)
{
if
(
action
==
null
)
{
action
=
'
toggle
'
;
}
if
(
action
===
'
toggle
'
)
{
this
.
triggerOpenSidebar
();
}
if
(
action
===
'
open
'
)
{
if
(
!
this
.
isOpen
())
{
this
.
triggerOpenSidebar
();
}
}
if
(
action
===
'
hide
'
)
{
if
(
this
.
isOpen
())
{
return
this
.
triggerOpenSidebar
();
}
}
};
Sidebar
.
prototype
.
isOpen
=
function
()
{
return
this
.
sidebar
.
is
(
'
.right-sidebar-expanded
'
);
};
Sidebar
.
prototype
.
isOpen
=
function
()
{
return
this
.
sidebar
.
is
(
'
.right-sidebar-expanded
'
);
};
Sidebar
.
prototype
.
getBlock
=
function
(
name
)
{
return
this
.
sidebar
.
find
(
"
.block.
"
+
name
);
};
Sidebar
.
prototype
.
getBlock
=
function
(
name
)
{
return
this
.
sidebar
.
find
(
"
.block.
"
+
name
);
};
return
Sidebar
;
})();
}).
call
(
window
);
export
default
Sidebar
;
app/assets/javascripts/shortcuts_issuable.js
View file @
f8cda25d
/* global Mousetrap */
/* global sidebar */
import
_
from
'
underscore
'
;
import
'
mousetrap
'
;
import
Sidebar
from
'
./right_sidebar
'
;
import
ShortcutsNavigation
from
'
./shortcuts_navigation
'
;
import
{
CopyAsGFM
}
from
'
./behaviors/copy_as_gfm
'
;
...
...
@@ -69,7 +69,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
}
static
openSidebarDropdown
(
name
)
{
sidebar
.
openDropdown
(
name
);
Sidebar
.
instance
.
openDropdown
(
name
);
return
false
;
}
}
app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
View file @
f8cda25d
...
...
@@ -6,7 +6,7 @@ Vue.use(VueResource);
export
default
class
MRWidgetService
{
constructor
(
endpoints
)
{
this
.
mergeResource
=
Vue
.
resource
(
endpoints
.
mergePath
);
this
.
mergeCheckResource
=
Vue
.
resource
(
endpoints
.
statusPath
);
this
.
mergeCheckResource
=
Vue
.
resource
(
`
${
endpoints
.
statusPath
}
?serializer=widget`
);
this
.
cancelAutoMergeResource
=
Vue
.
resource
(
endpoints
.
cancelAutoMergePath
);
this
.
removeWIPResource
=
Vue
.
resource
(
endpoints
.
removeWIPPath
);
this
.
removeSourceBranchResource
=
Vue
.
resource
(
endpoints
.
sourceBranchPath
);
...
...
app/assets/stylesheets/framework/stacked-progress-bar.scss
View file @
f8cda25d
...
...
@@ -11,12 +11,14 @@
.status-neutral
,
.status-red
,
{
height
:
100%
;
font-size
:
$tooltip-font-size
;
font-weight
:
normal
;
color
:
$white-light
;
line-height
:
20px
;
&
.has-value
{
padding
:
0
10px
;
min-width
:
25px
;
padding
:
0
5px
;
}
&
:hover
{
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
f8cda25d
...
...
@@ -133,7 +133,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.
new
(
project
,
current_user
,
wip_event:
'unwip'
)
.
execute
(
@merge_request
)
render
json:
serialize
r
.
represen
t
(
@merge_request
)
render
json:
serialize
_widge
t
(
@merge_request
)
end
def
commit_change_content
...
...
@@ -149,7 +149,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
.
new
(
@project
,
current_user
)
.
cancel
(
@merge_request
)
render
json:
serialize
r
.
represen
t
(
@merge_request
)
render
json:
serialize
_widge
t
(
@merge_request
)
end
def
merge
...
...
@@ -313,6 +313,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
end
def
serialize_widget
(
merge_request
)
serializer
.
represent
(
merge_request
,
serializer:
'widget'
)
end
def
serializer
MergeRequestSerializer
.
new
(
current_user:
current_user
,
project:
merge_request
.
project
)
end
...
...
app/helpers/issuables_helper.rb
View file @
f8cda25d
...
...
@@ -32,7 +32,7 @@ module IssuablesHelper
end
end
def
serialize_issuable
(
issuable
)
def
serialize_issuable
(
issuable
,
serializer:
nil
)
serializer_klass
=
case
issuable
when
Issue
IssueSerializer
...
...
@@ -42,7 +42,7 @@ module IssuablesHelper
serializer_klass
.
new
(
current_user:
current_user
,
project:
issuable
.
project
)
.
represent
(
issuable
)
.
represent
(
issuable
,
serializer:
serializer
)
.
to_json
end
...
...
app/serializers/issuable_entity.rb
View file @
f8cda25d
...
...
@@ -3,14 +3,6 @@ class IssuableEntity < Grape::Entity
expose
:id
expose
:iid
expose
:author_id
expose
:description
expose
:lock_version
expose
:milestone_id
expose
:title
expose
:updated_by_id
expose
:created_at
expose
:updated_at
expose
:milestone
,
using:
API
::
Entities
::
Milestone
expose
:labels
,
using:
LabelEntity
end
app/serializers/issuable_sidebar_entity.rb
View file @
f8cda25d
class
IssuableSidebarEntity
<
Grape
::
Entity
include
TimeTrackableEntity
include
RequestAwareEntity
prepend
::
EE
::
IssuableSidebarEntity
...
...
@@ -9,9 +10,4 @@ class IssuableSidebarEntity < Grape::Entity
expose
:subscribed
do
|
issuable
|
issuable
.
subscribed?
(
request
.
current_user
,
issuable
.
project
)
end
expose
:time_estimate
expose
:total_time_spent
expose
:human_time_estimate
expose
:human_total_time_spent
end
app/serializers/issue_entity.rb
View file @
f8cda25d
...
...
@@ -2,7 +2,15 @@ class IssueEntity < IssuableEntity
include
TimeTrackableEntity
expose
:state
expose
:milestone_id
expose
:updated_by_id
expose
:created_at
expose
:updated_at
expose
:deleted_at
expose
:milestone
,
using:
API
::
Entities
::
Milestone
expose
:labels
,
using:
LabelEntity
expose
:lock_version
expose
:author_id
expose
:confidential
expose
:discussion_locked
expose
:assignees
,
using:
API
::
Entities
::
UserBasic
...
...
app/serializers/merge_request_serializer.rb
View file @
f8cda25d
class
MergeRequestSerializer
<
BaseSerializer
# This overrided method takes care of which entity should be used
# to serialize the `merge_request` based on `
basic
` key in `opts` param.
# to serialize the `merge_request` based on `
serializer
` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope.
def
represent
(
merge_request
,
opts
=
{})
entity
=
case
opts
[
:serializer
]
when
'basic'
,
'sidebar'
MergeRequestBasicEntity
else
MergeRequestEntity
when
'widget'
MergeRequest
Widget
Entity
end
super
(
merge_request
,
opts
,
entity
)
...
...
app/serializers/merge_request_entity.rb
→
app/serializers/merge_request_
widget_
entity.rb
View file @
f8cda25d
class
MergeRequestEntity
<
IssuableEntity
include
TimeTrackableEntity
prepend
::
EE
::
MergeRequestEntity
class
MergeRequestWidgetEntity
<
IssuableEntity
prepend
::
EE
::
MergeRequestWidgetEntity
expose
:state
expose
:deleted_at
expose
:in_progress_merge_commit_sha
expose
:merge_commit_sha
expose
:merge_error
...
...
app/views/projects/merge_requests/show.html.haml
View file @
f8cda25d
...
...
@@ -21,7 +21,7 @@
-# haml-lint:disable InlineJavaScript
:javascript
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
mrWidgetData
=
#{
serialize_issuable
(
@merge_request
)
}
window
.
gl
.
mrWidgetData
=
#{
serialize_issuable
(
@merge_request
,
serializer:
'widget'
)
}
// Append static, server-generated data not included in merge request entity (EE-Only)
// Object.assign would be useful here, but it blows up Phantom.js in tests
...
...
app/views/shared/empty_states/_issues.html.haml
View file @
f8cda25d
...
...
@@ -8,16 +8,17 @@
=
image_tag
'illustrations/issues.svg'
.col-xs-12
.text-content
-
if
has_button
&&
current_user
-
if
current_user
%h4
=
_
(
"The Issue Tracker is the place to add things that need to be improved or solved in a project"
)
%p
=
_
(
"Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
)
.text-center
-
if
project_select_button
=
render
'shared/new_project_item_select'
,
path:
'issues/new'
,
label:
'New issue'
,
type: :issues
-
else
=
link_to
'New issue'
,
button_path
,
class:
'btn btn-success'
,
title:
'New issue'
,
id:
'new_issue_link'
-
if
has_button
.text-center
-
if
project_select_button
=
render
'shared/new_project_item_select'
,
path:
'issues/new'
,
label:
'New issue'
,
type: :issues
-
else
=
link_to
'New issue'
,
button_path
,
class:
'btn btn-success'
,
title:
'New issue'
,
id:
'new_issue_link'
-
else
%h4
.text-center
=
_
(
"There are no issues to show"
)
%p
...
...
changelogs/unreleased-ee/4330-show-item-count-in-tooltip.yml
0 → 100644
View file @
f8cda25d
---
title
:
'
Geo:
Show
sync
percent
on
bar
graph
and
count
within
tooltips'
merge_request
:
3794
author
:
type
:
changed
changelogs/unreleased/osw-isolate-mr-widget-exposed-attributes.yml
0 → 100644
View file @
f8cda25d
---
title
:
Stop sending milestone and labels data over the wire for MR widget requests
merge_request
:
author
:
type
:
performance
doc/articles/laravel_with_gitlab_and_envoy/index.md
View file @
f8cda25d
...
...
@@ -502,8 +502,8 @@ stages:
unit_test
:
stage
:
test
script
:
-
composer install
-
cp .env.example .env
-
composer install
-
php artisan key:generate
-
php artisan migrate
-
vendor/bin/phpunit
...
...
doc/workflow/repository_mirroring.md
View file @
f8cda25d
...
...
@@ -84,9 +84,19 @@ this branch to prevent any changes from being lost.
![
Diverged branch
](
repository_mirroring/repository_mirroring_diverged_branch.png
)
### Trigger update using API
>[Introduced][ee-3453] in GitLab Enterprise Edition 10.3.
Pull mirroring uses polling to detect new branches and commits added upstream,
often many minutes afterwards. If you notify GitLab by
[
API
][
pull-api
]
, updates
will be pulled immediately.
Read the
[
Pull Mirror Trigger API docs
][
pull-api
]
.
### Pull only protected branches
>[Introduced][ee-3326] in Git
l
ab Enterprise Edition 10.3.
>[Introduced][ee-3326] in Git
L
ab Enterprise Edition 10.3.
You can choose to only pull the protected branches from your remote repository to GitLab.
...
...
@@ -199,45 +209,50 @@ If you need to change the key at any time, you can press the `Regenerate key`
button to do so. You'll have to update the source repository with the new key
to keep the mirror running.
## How it works
##
#
How it works
Once you activate the pull mirroring feature, the mirror will be inserted into
a queue.
A scheduler will start every minute and schedule a fixed amount of mirrors for update, based
on the configured maximum capacity.
Once you activate the pull mirroring feature, the mirror will be inserted into
a queue. A scheduler will start every minute and schedule a fixed amount of
mirrors for update, based
on the configured maximum capacity.
If the mirror successfully updates it will be enqueued once again with a small
backoff
period.
If the mirror successfully updates it will be enqueued once again with a small
backoff
period.
If the mirror fails (eg: branch diverged from upstream), the project's
backoff
period will be penalized each time it fails up to a maximum amount of time.
If the mirror fails (eg: branch diverged from upstream), the project's
backoff
period will be penalized each time it fails up to a maximum amount of time.
## Pushing to a remote repository
>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise Edition 8.7.
>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in
GitLab Enterprise Edition 8.7.
For an existing project, you can set up
mirror pushing by visiting
your project's
For an existing project, you can set up
push mirror from
your project's
**Settings ➔ Repository**
and searching for the "Push to a remote repository"
section. Check the "Remote mirror repository" box and fill in the Git URL of the
repository to push to. Hit
**Save changes**
for the changes to take effect.
section. Check the "Remote mirror repository" box and fill in the Git URL of
the repository to push to. Click
**Save changes**
for the changes to take
effect.
![
Push settings
](
repository_mirroring/repository_mirroring_push_settings.png
)
Similarly to the pull mirroring, since the upstream repository functions as a
mirror to the repository in GitLab, you are advised not to push commits directly
to the mirrored repository. Instead, all changes will end up in the mirrored repository
whenever commits are pushed to GitLab, or when a
[
forced update
](
#forcing-an-update
)
is initiated.
Similarly to pull mirroring, when push mirroring is enabled, you are advised
not to push commits directly to the mirrored repository to prevent the mirror
diverging. All changes will end up in the mirrored repository whenever commits
are pushed to GitLab, or when a
[
forced update
](
#forcing-an-update
)
is
initiated.
Pushes into GitLab are automatically pushed to the remote mirror at least once every 5 minutes
after they come in or 1 minute if
**push only protected branches**
is enabled.
Pushes into GitLab are automatically pushed to the remote mirror at least once
every 5 minutes after they are received or once every minute if
**
push only
protected branches
**
is enabled.
In case of a diverged branch, you will see an error indicated at the
**Mirror
repository**
settings.
In case of a diverged branch, you will see an error indicated at the
**
Mirror
repository
**
settings.
![
Diverged branch
](
repository_mirroring/repository_mirroring_diverged_branch_push.png
)
![
Diverged branch
](
repository_mirroring/repository_mirroring_diverged_branch_push.png
)
### Push only protected branches
>[Introduced][ee-3350] in Git
l
ab Enterprise Edition 10.3.
>[Introduced][ee-3350] in Git
L
ab Enterprise Edition 10.3.
You can choose to only push your protected branches from GitLab to your remote repository.
...
...
@@ -259,7 +274,7 @@ To set up a mirror from GitLab to GitHub, you need to follow these steps:
1.
And either wait or trigger the "Update Now" button:
![update now](repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png)
## Forcing an update
While mirrors are scheduled to update automatically, you can always force an update (either
**push**
or
...
...
@@ -270,19 +285,38 @@ While mirrors are scheduled to update automatically, you can always force an upd
-
in the tags page
-
in the
**Mirror repository**
settings page
## Using both mirroring methods at the same time
## Bidirectional mirroring
> **Warning:** There is no bidirectional support without conflicts. If you
> configure a repository to pull and push to a second remote, there is no
> guarantee that it will update correctly on both remotes. If you configure
> a repository for bidirectional mirroring, you should consider when conflicts
> occur who and how they will be resolved.
Rewriting any mirrored commit on either remote will cause conflicts and
mirroring to fail. This can be prevented by
[
only pulling protected branches
](
#pull-only-protected-branches
)
and
[
only pushing protected branches
](
#push-only-protected-branches
)
. You should protect the branches you wish to
mirror on both remotes to prevent conflicts caused by rewriting history.
Currently there is no bidirectional support without conflicts. That means that
if you configure a repository to both pull and push to a second one, there is
no guarantee that it will update correctly on both remotes.
You can try
[
configuring custom Git hooks
][
hooks
]
on the GitLab server in order
to
resolve this issue
.
Bidirectional mirroring also creates a race condition where commits to the same
branch in close proximity will cause conflicts. The race condition can be
mitigated by reducing the mirroring delay by using a Push event webhook to
trigger an immediate pull to GitLab. Push mirroring from GitLab is rate limited
to
once per minute when only push mirroring protected branches
.
It may be possible to implement a locking mechanism using the server-side
`pre-receive`
hook to prevent the race condition. Read about
[
configuring
custom Git hooks
][
hooks
]
on the GitLab server.
[
ee-51
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51
[
ee-2551
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551
[
ee-3117
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117
[
ee-3326
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326
[
ee-3350
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350
[
ee-3453
]:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453
[
perms
]:
../user/permissions.md
[
hooks
]:
https://docs.gitlab.com/ee
/administration/custom_hooks.html
[
hooks
]:
..
/administration/custom_hooks.html
[
deploy-key
]:
../ssh/README.md#deploy-keys
[
webhook
]:
../user/project/integrations/webhooks.html#push-events
[
pull-api
]:
../api/projects.html#start-the-pull-mirroring-process-for-a-project
ee/app/serializers/ee/merge_request_entity.rb
→
ee/app/serializers/ee/merge_request_
widget_
entity.rb
View file @
f8cda25d
module
EE
module
MergeRequestEntity
module
MergeRequest
Widget
Entity
extend
ActiveSupport
::
Concern
prepended
do
...
...
qa/qa.rb
View file @
f8cda25d
...
...
@@ -12,6 +12,27 @@ module QA
autoload
:Browser
,
'qa/runtime/browser'
end
##
# GitLab QA fabrication mechanisms
#
module
Factory
autoload
:Base
,
'qa/factory/base'
module
Resource
autoload
:Sandbox
,
'qa/factory/resource/sandbox'
autoload
:Group
,
'qa/factory/resource/group'
autoload
:Project
,
'qa/factory/resource/project'
end
module
Repository
autoload
:Push
,
'qa/factory/repository/push'
end
module
Settings
autoload
:HashedStorage
,
'qa/factory/settings/hashed_storage'
end
end
##
# GitLab QA Scenarios
#
...
...
@@ -34,31 +55,6 @@ module QA
autoload
:Mattermost
,
'qa/scenario/test/integration/mattermost'
end
end
##
# GitLab instance scenarios.
#
module
Gitlab
module
Group
autoload
:Create
,
'qa/scenario/gitlab/group/create'
end
module
Project
autoload
:Create
,
'qa/scenario/gitlab/project/create'
end
module
Repository
autoload
:Push
,
'qa/scenario/gitlab/repository/push'
end
module
Sandbox
autoload
:Prepare
,
'qa/scenario/gitlab/sandbox/prepare'
end
module
Admin
autoload
:HashedStorage
,
'qa/scenario/gitlab/admin/hashed_storage'
end
end
end
##
...
...
qa/qa/ee.rb
View file @
f8cda25d
...
...
@@ -16,18 +16,18 @@ module QA
end
end
module
Scenario
module
Factory
autoload
:License
,
'qa/ee/factory/license'
module
Geo
autoload
:Node
,
'qa/ee/
scenario
/geo/node'
autoload
:Node
,
'qa/ee/
factory
/geo/node'
end
end
module
Scenario
module
Test
autoload
:Geo
,
'qa/ee/scenario/test/geo'
end
module
License
autoload
:Add
,
'qa/ee/scenario/license/add'
end
end
end
end
qa/qa/ee/
scenario
/geo/node.rb
→
qa/qa/ee/
factory
/geo/node.rb
View file @
f8cda25d
module
QA
module
EE
module
Scenario
module
Factory
module
Geo
class
Node
<
QA
::
Scenario
::
Templat
e
class
Node
<
QA
::
Factory
::
Bas
e
attr_accessor
:address
def
perform
def
fabricate!
QA
::
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
QA
::
Page
::
Main
::
Menu
.
act
{
go_to_admin_area
}
QA
::
Page
::
Admin
::
Menu
.
act
{
go_to_geo_nodes
}
...
...
qa/qa/ee/factory/license.rb
0 → 100644
View file @
f8cda25d
module
QA
module
EE
module
Factory
class
License
<
QA
::
Factory
::
Base
def
fabricate!
(
license
)
QA
::
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
QA
::
Page
::
Main
::
Menu
.
act
{
go_to_admin_area
}
QA
::
Page
::
Admin
::
Menu
.
act
{
go_to_license
}
EE
::
Page
::
Admin
::
License
.
act
(
license
)
do
|
key
|
add_new_license
(
key
)
if
no_license?
end
QA
::
Page
::
Main
::
Menu
.
act
{
sign_out
}
end
end
end
end
end
qa/qa/ee/scenario/license/add.rb
deleted
100644 → 0
View file @
be0369a1
module
QA
module
EE
module
Scenario
module
License
class
Add
<
QA
::
Scenario
::
Template
def
perform
(
license
)
QA
::
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
QA
::
Page
::
Main
::
Menu
.
act
{
go_to_admin_area
}
QA
::
Page
::
Admin
::
Menu
.
act
{
go_to_license
}
EE
::
Page
::
Admin
::
License
.
act
(
license
)
do
|
key
|
add_new_license
(
key
)
if
no_license?
end
QA
::
Page
::
Main
::
Menu
.
act
{
sign_out
}
end
end
end
end
end
end
qa/qa/ee/scenario/test/geo.rb
View file @
f8cda25d
...
...
@@ -39,32 +39,26 @@ module QA
end
def
add_license
# TODO EE license to Runtime.license, gitlab-org/gitlab-qa#86
#
puts
'Adding GitLab EE license ...'
QA
::
Runtime
::
Browser
.
visit
(
:geo_primary
,
QA
::
Page
::
Main
::
Login
)
do
Scenario
::
License
::
Add
.
perform
(
ENV
[
'EE_LICENSE'
])
Factory
::
License
.
fabricate!
(
ENV
[
'EE_LICENSE'
])
end
end
def
enable_hashed_storage
# TODO, Factory::HashedStorage - gitlab-org/gitlab-qa#86
#
puts
'Enabling hashed repository storage setting ...'
QA
::
Runtime
::
Browser
.
visit
(
:geo_primary
,
QA
::
Page
::
Main
::
Login
)
do
QA
::
Scenario
::
Gitlab
::
Admin
::
HashedStorage
.
perform
(
:enabled
)
QA
::
Factory
::
Settings
::
HashedStorage
.
fabricate!
(
:enabled
)
end
end
def
add_secondary_node
# TODO, Factory::Geo::Node - gitlab-org/gitlab-qa#86
#
puts
'Adding new Geo secondary node ...'
QA
::
Runtime
::
Browser
.
visit
(
:geo_primary
,
QA
::
Page
::
Main
::
Login
)
do
Scenario
::
Geo
::
Node
.
perform
do
|
node
|
Factory
::
Geo
::
Node
.
fabricate!
do
|
node
|
node
.
address
=
QA
::
Runtime
::
Scenario
.
geo_secondary_address
end
end
...
...
qa/qa/ee/strategy.rb
View file @
f8cda25d
...
...
@@ -11,7 +11,7 @@ module QA
return
unless
ENV
[
'EE_LICENSE'
]
QA
::
Runtime
::
Browser
.
visit
(
:gitlab
,
QA
::
Page
::
Main
::
Login
)
do
EE
::
Scenario
::
License
::
Add
.
perform
(
ENV
[
'EE_LICENSE'
])
EE
::
Factory
::
License
.
fabricate!
(
ENV
[
'EE_LICENSE'
])
end
end
end
...
...
qa/qa/factory/base.rb
0 → 100644
View file @
f8cda25d
module
QA
module
Factory
class
Base
def
self
.
fabricate!
(
*
args
)
new
.
tap
do
|
factory
|
yield
factory
if
block_given?
return
factory
.
fabricate!
(
*
args
)
end
end
def
fabricate!
(
*
_args
)
raise
NotImplementedError
end
end
end
end
qa/qa/factory/repository/push.rb
0 → 100644
View file @
f8cda25d
require
"pry-byebug"
module
QA
module
Factory
module
Repository
class
Push
<
Factory
::
Base
PAGE_REGEX_CHECK
=
%r{
\/
#{
Runtime
::
Namespace
.
sandbox_name
}
\/
qa-test[^
\/
]+
\/
{1}[^
\/
]+
\z
}
.
freeze
attr_writer
:file_name
,
:file_content
,
:commit_message
,
:branch_name
def
initialize
@file_name
=
'file.txt'
@file_content
=
'# This is test project'
@commit_message
=
"Add
#{
@file_name
}
"
@branch_name
=
'master'
end
def
fabricate!
Git
::
Repository
.
perform
do
|
repository
|
repository
.
location
=
Page
::
Project
::
Show
.
act
do
unless
PAGE_REGEX_CHECK
.
match
(
current_path
)
raise
"To perform this scenario the current page should be project show."
end
choose_repository_clone_http
repository_location
end
repository
.
use_default_credentials
repository
.
clone
repository
.
configure_identity
(
'GitLab QA'
,
'root@gitlab.com'
)
repository
.
add_file
(
@file_name
,
@file_content
)
repository
.
commit
(
@commit_message
)
repository
.
push_changes
(
@branch_name
)
end
end
end
end
end
end
qa/qa/factory/resource/group.rb
0 → 100644
View file @
f8cda25d
module
QA
module
Factory
module
Resource
class
Group
<
Factory
::
Base
attr_writer
:path
,
:description
def
initialize
@path
=
Runtime
::
Namespace
.
name
@description
=
"QA test run at
#{
Runtime
::
Namespace
.
time
}
"
end
def
fabricate!
Page
::
Group
::
New
.
perform
do
|
group
|
group
.
set_path
(
@path
)
group
.
set_description
(
@description
)
group
.
set_visibility
(
'Private'
)
group
.
create
end
end
end
end
end
end
qa/qa/factory/resource/project.rb
0 → 100644
View file @
f8cda25d
require
'securerandom'
module
QA
module
Factory
module
Resource
class
Project
<
Factory
::
Base
attr_writer
:description
def
name
=
(
name
)
@name
=
"
#{
name
}
-
#{
SecureRandom
.
hex
(
8
)
}
"
end
def
fabricate!
Factory
::
Resource
::
Sandbox
.
fabricate!
Page
::
Group
::
Show
.
perform
do
|
page
|
if
page
.
has_subgroup?
(
Runtime
::
Namespace
.
name
)
page
.
go_to_subgroup
(
Runtime
::
Namespace
.
name
)
else
page
.
go_to_new_subgroup
Factory
::
Resource
::
Group
.
fabricate!
do
|
group
|
group
.
path
=
Runtime
::
Namespace
.
name
end
end
page
.
go_to_new_project
end
Page
::
Project
::
New
.
perform
do
|
page
|
page
.
choose_test_namespace
page
.
choose_name
(
@name
)
page
.
add_description
(
@description
)
page
.
create_new_project
end
end
end
end
end
end
qa/qa/factory/resource/sandbox.rb
0 → 100644
View file @
f8cda25d
module
QA
module
Factory
module
Resource
##
# Ensure we're in our sandbox namespace, either by navigating to it or by
# creating it if it doesn't yet exist.
#
class
Sandbox
<
Factory
::
Base
def
fabricate!
Page
::
Main
::
Menu
.
act
{
go_to_groups
}
Page
::
Dashboard
::
Groups
.
perform
do
|
page
|
if
page
.
has_group?
(
Runtime
::
Namespace
.
sandbox_name
)
page
.
go_to_group
(
Runtime
::
Namespace
.
sandbox_name
)
else
page
.
go_to_new_group
Resource
::
Group
.
fabricate!
do
|
group
|
group
.
path
=
Runtime
::
Namespace
.
sandbox_name
group
.
description
=
'GitLab QA Sandbox'
end
end
end
end
end
end
end
end
qa/qa/factory/settings/hashed_storage.rb
0 → 100644
View file @
f8cda25d
module
QA
module
Factory
module
Settings
class
HashedStorage
<
Factory
::
Base
def
fabricate!
(
*
traits
)
raise
ArgumentError
unless
traits
.
include?
(
:enabled
)
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
Page
::
Main
::
Menu
.
act
{
go_to_admin_area
}
Page
::
Admin
::
Menu
.
act
{
go_to_settings
}
Page
::
Admin
::
Settings
.
act
do
enable_hashed_storage
save_settings
end
QA
::
Page
::
Main
::
Menu
.
act
{
sign_out
}
end
end
end
end
end
qa/qa/scenario/gitlab/admin/hashed_storage.rb
deleted
100644 → 0
View file @
be0369a1
module
QA
module
Scenario
module
Gitlab
module
Admin
class
HashedStorage
<
Scenario
::
Template
def
perform
(
*
traits
)
raise
ArgumentError
unless
traits
.
include?
(
:enabled
)
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
Page
::
Main
::
Menu
.
act
{
go_to_admin_area
}
Page
::
Admin
::
Menu
.
act
{
go_to_settings
}
Page
::
Admin
::
Settings
.
act
do
enable_hashed_storage
save_settings
end
QA
::
Page
::
Main
::
Menu
.
act
{
sign_out
}
end
end
end
end
end
end
qa/qa/scenario/gitlab/group/create.rb
deleted
100644 → 0
View file @
be0369a1
require
'securerandom'
module
QA
module
Scenario
module
Gitlab
module
Group
class
Create
<
Scenario
::
Template
attr_writer
:path
,
:description
def
initialize
@path
=
Runtime
::
Namespace
.
name
@description
=
"QA test run at
#{
Runtime
::
Namespace
.
time
}
"
end
def
perform
Page
::
Group
::
New
.
perform
do
|
group
|
group
.
set_path
(
@path
)
group
.
set_description
(
@description
)
group
.
set_visibility
(
'Private'
)
group
.
create
end
end
end
end
end
end
end
qa/qa/scenario/gitlab/project/create.rb
deleted
100644 → 0
View file @
be0369a1
require
'securerandom'
module
QA
module
Scenario
module
Gitlab
module
Project
class
Create
<
Scenario
::
Template
attr_writer
:description
def
name
=
(
name
)
@name
=
"
#{
name
}
-
#{
SecureRandom
.
hex
(
8
)
}
"
end
def
perform
Scenario
::
Gitlab
::
Sandbox
::
Prepare
.
perform
Page
::
Group
::
Show
.
perform
do
|
page
|
if
page
.
has_subgroup?
(
Runtime
::
Namespace
.
name
)
page
.
go_to_subgroup
(
Runtime
::
Namespace
.
name
)
else
page
.
go_to_new_subgroup
Scenario
::
Gitlab
::
Group
::
Create
.
perform
do
|
group
|
group
.
path
=
Runtime
::
Namespace
.
name
end
end
page
.
go_to_new_project
end
Page
::
Project
::
New
.
perform
do
|
page
|
page
.
choose_test_namespace
page
.
choose_name
(
@name
)
page
.
add_description
(
@description
)
page
.
create_new_project
end
end
end
end
end
end
end
qa/qa/scenario/gitlab/repository/push.rb
deleted
100644 → 0
View file @
be0369a1
require
"pry-byebug"
module
QA
module
Scenario
module
Gitlab
module
Repository
class
Push
<
Scenario
::
Template
PAGE_REGEX_CHECK
=
%r{
\/
#{
Runtime
::
Namespace
.
sandbox_name
}
\/
qa-test[^
\/
]+
\/
{1}[^
\/
]+
\z
}
.
freeze
attr_writer
:file_name
,
:file_content
,
:commit_message
,
:branch_name
def
initialize
@file_name
=
'file.txt'
@file_content
=
'# This is test project'
@commit_message
=
"Add
#{
@file_name
}
"
@branch_name
=
'master'
end
def
perform
Git
::
Repository
.
perform
do
|
repository
|
repository
.
location
=
Page
::
Project
::
Show
.
act
do
unless
PAGE_REGEX_CHECK
.
match
(
current_path
)
raise
"To perform this scenario the current page should be project show."
end
choose_repository_clone_http
repository_location
end
repository
.
use_default_credentials
repository
.
clone
repository
.
configure_identity
(
'GitLab QA'
,
'root@gitlab.com'
)
repository
.
add_file
(
@file_name
,
@file_content
)
repository
.
commit
(
@commit_message
)
repository
.
push_changes
(
@branch_name
)
end
end
end
end
end
end
end
qa/qa/scenario/gitlab/sandbox/prepare.rb
deleted
100644 → 0
View file @
be0369a1
module
QA
module
Scenario
module
Gitlab
module
Sandbox
# Ensure we're in our sandbox namespace, either by navigating to it or
# by creating it if it doesn't yet exist
class
Prepare
<
Scenario
::
Template
def
perform
Page
::
Main
::
Menu
.
act
{
go_to_groups
}
Page
::
Dashboard
::
Groups
.
perform
do
|
page
|
if
page
.
has_group?
(
Runtime
::
Namespace
.
sandbox_name
)
page
.
go_to_group
(
Runtime
::
Namespace
.
sandbox_name
)
else
page
.
go_to_new_group
Scenario
::
Gitlab
::
Group
::
Create
.
perform
do
|
group
|
group
.
path
=
Runtime
::
Namespace
.
sandbox_name
group
.
description
=
'QA sandbox'
end
end
end
end
end
end
end
end
end
qa/qa/specs/features/ee/geo/replication_spec.rb
View file @
f8cda25d
...
...
@@ -4,9 +4,9 @@ module QA
Runtime
::
Browser
.
visit
(
:geo_primary
,
QA
::
Page
::
Main
::
Login
)
do
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
Scenario
::
Gitlab
::
Project
::
Create
.
perform
do
|
scenario
|
scenario
.
name
=
'geo-project'
scenario
.
description
=
'Geo test project'
Factory
::
Resource
::
Project
.
fabricate!
do
|
project
|
project
.
name
=
'geo-project'
project
.
description
=
'Geo test project'
end
geo_project_name
=
Page
::
Project
::
Show
.
act
{
project_name
}
...
...
qa/qa/specs/features/project/create_spec.rb
View file @
f8cda25d
...
...
@@ -4,7 +4,7 @@ module QA
Runtime
::
Browser
.
visit
(
:gitlab
,
Page
::
Main
::
Login
)
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
Scenario
::
Gitlab
::
Project
::
Create
.
perform
do
|
project
|
Factory
::
Resource
::
Project
.
fabricate!
do
|
project
|
project
.
name
=
'awesome-project'
project
.
description
=
'create awesome project test'
end
...
...
qa/qa/specs/features/repository/clone_spec.rb
View file @
f8cda25d
...
...
@@ -12,7 +12,7 @@ module QA
Runtime
::
Browser
.
visit
(
:gitlab
,
Page
::
Main
::
Login
)
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
Scenario
::
Gitlab
::
Project
::
Create
.
perform
do
|
scenario
|
Factory
::
Resource
::
Project
.
fabricate!
do
|
scenario
|
scenario
.
name
=
'project-with-code'
scenario
.
description
=
'project for git clone tests'
end
...
...
qa/qa/specs/features/repository/push_spec.rb
View file @
f8cda25d
...
...
@@ -5,12 +5,12 @@ module QA
Runtime
::
Browser
.
visit
(
:gitlab
,
Page
::
Main
::
Login
)
Page
::
Main
::
Login
.
act
{
sign_in_using_credentials
}
Scenario
::
Gitlab
::
Project
::
Create
.
perform
do
|
scenario
|
Factory
::
Resource
::
Project
.
fabricate!
do
|
scenario
|
scenario
.
name
=
'project_with_code'
scenario
.
description
=
'project with repository'
end
Scenario
::
Gitlab
::
Repository
::
Push
.
perform
do
|
scenario
|
Factory
::
Repository
::
Push
.
fabricate!
do
|
scenario
|
scenario
.
file_name
=
'README.md'
scenario
.
file_content
=
'# This is test project'
scenario
.
commit_message
=
'Add README.md'
...
...
spec/controllers/projects/merge_requests_controller_spec.rb
View file @
f8cda25d
...
...
@@ -91,11 +91,11 @@ describe Projects::MergeRequestsController do
end
end
context
'with
out basic
serializer param'
do
it
'renders
the merge request in the json format
'
do
go
(
format: :json
)
context
'with
widget
serializer param'
do
it
'renders
widget MR entity as json
'
do
go
(
serializer:
'widget'
,
format: :json
)
expect
(
response
).
to
match_response_schema
(
'entities/merge_request'
)
expect
(
response
).
to
match_response_schema
(
'entities/merge_request
_widget
'
)
end
end
end
...
...
spec/ee/spec/serializers/epic_entity_spec.rb
View file @
f8cda25d
...
...
@@ -10,8 +10,7 @@ describe EpicEntity do
subject
{
described_class
.
new
(
resource
,
request:
request
).
as_json
}
it
'has Issuable attributes'
do
expect
(
subject
).
to
include
(
:id
,
:iid
,
:author_id
,
:description
,
:lock_version
,
:milestone_id
,
:title
,
:updated_by_id
,
:created_at
,
:updated_at
,
:milestone
,
:labels
)
expect
(
subject
).
to
include
(
:id
,
:iid
,
:description
,
:title
)
end
it
'has epic specific attributes'
do
...
...
spec/ee/spec/serializers/merge_request_entity_spec.rb
→
spec/ee/spec/serializers/merge_request_
widget_
entity_spec.rb
View file @
f8cda25d
require
'spec_helper'
describe
MergeRequestEntity
do
describe
MergeRequest
Widget
Entity
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
:project
,
:repository
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
...
...
spec/features/issues_spec.rb
View file @
f8cda25d
...
...
@@ -8,765 +8,789 @@ describe 'Issues' do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
,
:public
)
}
before
do
sign_in
(
user
)
user2
=
create
(
:user
)
project
.
team
<<
[[
user
,
user2
],
:developer
]
end
describe
'while user is signed out'
do
describe
'empty state'
do
it
'user sees empty state'
do
visit
project_issues_path
(
project
)
describe
'Edit issue'
do
let!
(
:issue
)
do
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
)
expect
(
page
).
to
have_content
(
'Register / Sign In'
)
expect
(
page
).
to
have_content
(
'The Issue Tracker is the place to add things that need to be improved or solved in a project.'
)
expect
(
page
).
to
have_content
(
'You can register or sign in to create issues for this project.'
)
end
end
end
describe
'while user is signed in'
do
before
do
visit
edit_project_issue_path
(
project
,
issue
)
find
(
'.js-zen-enter'
).
click
end
it
'opens new issue popup'
do
expect
(
page
).
to
have_content
(
"Issue #
#{
issue
.
iid
}
"
)
end
end
sign_in
(
user
)
user2
=
create
(
:user
)
describe
'Editing issue assignee'
do
let!
(
:issue
)
do
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
)
project
.
team
<<
[[
user
,
user2
],
:developer
]
end
it
'allows user to select unassigned'
,
:js
do
visit
edit_project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
"Assignee
#{
user
.
name
}
"
describe
'empty state'
do
it
'user sees empty state'
do
visit
project_issues_path
(
project
)
first
(
'.js-user-search'
).
click
click_link
'Unassigned'
expect
(
page
).
to
have_content
(
'The Issue Tracker is the place to add things that need to be improved or solved in a project'
)
expect
(
page
).
to
have_content
(
'Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.'
)
expect
(
page
).
to
have_content
(
'New issue'
)
end
end
click_button
'Save changes'
describe
'Edit issue'
do
let!
(
:issue
)
do
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
)
end
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
'No assignee - assign yourself'
before
do
visit
edit_project_issue_path
(
project
,
issue
)
find
(
'.js-zen-enter'
).
click
end
expect
(
issue
.
reload
.
assignees
).
to
be_empty
it
'opens new issue popup'
do
expect
(
page
).
to
have_content
(
"Issue #
#{
issue
.
iid
}
"
)
end
end
end
describe
'due date'
,
:js
do
context
'on new form'
do
before
do
visit
new_project_issue_path
(
project
)
describe
'Editing issue assignee'
do
let!
(
:issue
)
do
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
)
end
it
'saves with due date'
do
date
=
Date
.
today
.
at_beginning_of_month
fill_in
'issue_title'
,
with:
'bug 345'
fill_in
'issue_description'
,
with:
'bug description'
find
(
'#issuable-due-date'
).
click
it
'allows user to select unassigned'
,
:js
do
visit
edit_project_issue_path
(
project
,
issue
)
page
.
within
'.pika-single'
do
click_button
date
.
day
end
expect
(
page
).
to
have_content
"Assignee
#{
user
.
name
}
"
expect
(
find
(
'#issuable-due-date'
).
value
).
to
eq
date
.
to_s
first
(
'.js-user-search'
).
click
click_link
'Unassigned'
click_button
'S
ubmit issue
'
click_button
'S
ave changes
'
page
.
within
'.issuable-sidebar'
do
expect
(
page
).
to
have_content
date
.
to_s
(
:medium
)
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
'No assignee - assign yourself'
end
expect
(
issue
.
reload
.
assignees
).
to
be_empty
end
end
context
'on edit form'
do
let
(
:issue
)
{
create
(
:issue
,
author:
user
,
project:
project
,
due_date:
Date
.
today
.
at_beginning_of_month
.
to_s
)
}
describe
'due date'
,
:js
do
context
'on new form'
do
before
do
visit
new_project_issue_path
(
project
)
end
before
do
visit
edit_project_issue_path
(
project
,
issue
)
end
it
'saves with due date'
do
date
=
Date
.
today
.
at_beginning_of_month
it
'saves with due date'
do
date
=
Date
.
today
.
at_beginning_of_month
fill_in
'issue_title'
,
with:
'bug 345'
fill_in
'issue_description'
,
with:
'bug description'
find
(
'#issuable-due-date'
).
click
expect
(
find
(
'#issuable-due-date'
).
value
).
to
eq
date
.
to_s
page
.
within
'.pika-single'
do
click_button
date
.
day
end
date
=
date
.
tomorrow
expect
(
find
(
'#issuable-due-date'
).
value
).
to
eq
date
.
to_s
fill_in
'issue_title'
,
with:
'bug 345'
fill_in
'issue_description'
,
with:
'bug description'
find
(
'#issuable-due-date'
).
click
click_button
'Submit issue'
page
.
within
'.pika-single'
do
click_button
date
.
day
page
.
within
'.issuable-sidebar'
do
expect
(
page
).
to
have_content
date
.
to_s
(
:medium
)
end
end
end
expect
(
find
(
'#issuable-due-date'
).
value
).
to
eq
date
.
to_s
click_button
'Save changes'
context
'on edit form'
do
let
(
:issue
)
{
create
(
:issue
,
author:
user
,
project:
project
,
due_date:
Date
.
today
.
at_beginning_of_month
.
to_s
)
}
page
.
within
'.issuable-sidebar'
do
expect
(
page
).
to
have_content
date
.
to_s
(
:medium
)
before
do
visit
edit_project_issue_path
(
project
,
issue
)
end
end
it
'warns about version conflict
'
do
issue
.
update
(
title:
"New title"
)
it
'saves with due date
'
do
date
=
Date
.
today
.
at_beginning_of_month
fill_in
'issue_title'
,
with:
'bug 345'
fill_in
'issue_description'
,
with:
'bug description'
expect
(
find
(
'#issuable-due-date'
).
value
).
to
eq
date
.
to_s
click_button
'Save changes'
date
=
date
.
tomorrow
expect
(
page
).
to
have_content
'Someone edited the issue the same time you did'
end
end
end
fill_in
'issue_title'
,
with:
'bug 345'
fill_in
'issue_description'
,
with:
'bug description'
find
(
'#issuable-due-date'
).
click
describe
'Issue info
'
do
it
'links to current issue in breadcrubs'
do
issue
=
create
(
:issue
,
project:
project
)
page
.
within
'.pika-single
'
do
click_button
date
.
day
end
visit
project_issue_path
(
project
,
issue
)
expect
(
find
(
'#issuable-due-date'
).
value
).
to
eq
date
.
to_s
expect
(
find
(
'.breadcrumbs-sub-title a'
)[
:href
]).
to
end_with
(
issue_path
(
issue
))
end
click_button
'Save changes'
it
'excludes award_emoji from comment count'
do
issue
=
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
,
title:
'foobar'
)
create
(
:award_emoji
,
awardable:
issue
)
page
.
within
'.issuable-sidebar'
do
expect
(
page
).
to
have_content
date
.
to_s
(
:medium
)
end
end
visit
project_issues_path
(
project
,
assignee_id:
user
.
id
)
it
'warns about version conflict'
do
issue
.
update
(
title:
"New title"
)
expect
(
page
).
to
have_content
'foobar'
expect
(
page
.
all
(
'.no-comments'
).
first
.
text
).
to
eq
"0"
end
fill_in
'issue_title'
,
with:
'bug 345'
fill_in
'issue_description'
,
with:
'bug description'
it
'shows weight on issue row'
do
create
(
:issue
,
author:
user
,
project:
project
,
weight:
2
)
click_button
'Save changes'
visit
project_issues_path
(
project
)
page
.
within
(
first
(
'.issuable-info'
))
do
expect
(
page
).
to
have_selector
(
'.fa-balance-scale'
)
expect
(
page
).
to
have_content
(
2
)
expect
(
page
).
to
have_content
'Someone edited the issue the same time you did'
end
end
end
end
describe
'Filter issue'
do
before
do
%w(foobar barbaz gitlab)
.
each
do
|
title
|
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
,
title:
title
)
end
describe
'Issue info'
do
it
'links to current issue in breadcrubs'
do
issue
=
create
(
:issue
,
project:
project
)
@issue
=
Issue
.
find_by
(
title:
'foobar'
)
@issue
.
milestone
=
create
(
:milestone
,
project:
project
)
@issue
.
assignees
=
[]
@issue
.
save
end
visit
project_issue_path
(
project
,
issue
)
let
(
:issue
)
{
@issue
}
expect
(
find
(
'.breadcrumbs-sub-title a'
)[
:href
]).
to
end_with
(
issue_path
(
issue
))
end
it
'allows filtering by issues with no specified assignee'
do
visit
project_issues_path
(
project
,
assignee_id:
IssuableFinder
::
NONE
)
it
'excludes award_emoji from comment count'
do
issue
=
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
,
title:
'foobar'
)
create
(
:award_emoji
,
awardable:
issue
)
expect
(
page
).
to
have_content
'foobar'
expect
(
page
).
not_to
have_content
'barbaz'
expect
(
page
).
not_to
have_content
'gitlab'
end
visit
project_issues_path
(
project
,
assignee_id:
user
.
id
)
it
'allows filtering by a specified assignee'
do
visit
project_issues_path
(
project
,
assignee_id:
user
.
id
)
expect
(
page
).
to
have_content
'foobar'
expect
(
page
.
all
(
'.no-comments'
).
first
.
text
).
to
eq
"0"
end
expect
(
page
).
not_to
have_content
'foobar'
expect
(
page
).
to
have_content
'barbaz'
expect
(
page
).
to
have_content
'gitlab'
end
end
it
'shows weight on issue row'
do
create
(
:issue
,
author:
user
,
project:
project
,
weight:
2
)
describe
'filter issue'
do
titles
=
%w[foo bar baz]
titles
.
each_with_index
do
|
title
,
index
|
let!
(
title
.
to_sym
)
do
create
(
:issue
,
title:
title
,
project:
project
,
created_at:
Time
.
now
-
(
index
*
60
))
visit
project_issues_path
(
project
)
page
.
within
(
first
(
'.issuable-info'
))
do
expect
(
page
).
to
have_selector
(
'.fa-balance-scale'
)
expect
(
page
).
to
have_content
(
2
)
end
end
end
let
(
:newer_due_milestone
)
{
create
(
:milestone
,
due_date:
'2013-12-11'
)
}
let
(
:later_due_milestone
)
{
create
(
:milestone
,
due_date:
'2013-12-12'
)
}
it
'sorts by newest'
do
visit
project_issues_path
(
project
,
sort:
sort_value_created_date
)
describe
'Filter issue'
do
before
do
%w(foobar barbaz gitlab)
.
each
do
|
title
|
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
,
title:
title
)
end
expect
(
first_issue
).
to
include
(
'foo'
)
expect
(
last_issue
).
to
include
(
'baz'
)
end
@issue
=
Issue
.
find_by
(
title:
'foobar'
)
@issue
.
milestone
=
create
(
:milestone
,
project:
project
)
@issue
.
assignees
=
[]
@issue
.
save
end
it
'sorts by most recently updated'
do
baz
.
updated_at
=
Time
.
now
+
100
baz
.
save
visit
project_issues_path
(
project
,
sort:
sort_value_recently_updated
)
let
(
:issue
)
{
@issue
}
expect
(
first_issue
).
to
include
(
'baz'
)
end
it
'allows filtering by issues with no specified assignee'
do
visit
project_issues_path
(
project
,
assignee_id:
IssuableFinder
::
NONE
)
describe
'sorting by due date'
do
before
do
foo
.
update
(
due_date:
1
.
day
.
from_now
)
bar
.
update
(
due_date:
6
.
days
.
from_now
)
expect
(
page
).
to
have_content
'foobar'
expect
(
page
).
not_to
have_content
'barbaz'
expect
(
page
).
not_to
have_content
'gitlab'
end
it
'
sorts by due dat
e'
do
visit
project_issues_path
(
project
,
sort:
sort_value_due_date
)
it
'
allows filtering by a specified assigne
e'
do
visit
project_issues_path
(
project
,
assignee_id:
user
.
id
)
expect
(
first_issue
).
to
include
(
'foo'
)
expect
(
page
).
not_to
have_content
'foobar'
expect
(
page
).
to
have_content
'barbaz'
expect
(
page
).
to
have_content
'gitlab'
end
end
it
'sorts by due date by excluding nil due dates'
do
bar
.
update
(
due_date:
nil
)
describe
'filter issue'
do
titles
=
%w[foo bar baz]
titles
.
each_with_index
do
|
title
,
index
|
let!
(
title
.
to_sym
)
do
create
(
:issue
,
title:
title
,
project:
project
,
created_at:
Time
.
now
-
(
index
*
60
))
end
end
let
(
:newer_due_milestone
)
{
create
(
:milestone
,
due_date:
'2013-12-11'
)
}
let
(
:later_due_milestone
)
{
create
(
:milestone
,
due_date:
'2013-12-12'
)
}
visit
project_issues_path
(
project
,
sort:
sort_value_due_date
)
it
'sorts by newest'
do
visit
project_issues_path
(
project
,
sort:
sort_value_created_date
)
expect
(
first_issue
).
to
include
(
'foo'
)
expect
(
last_issue
).
to
include
(
'baz'
)
end
context
'with a filter on labels'
do
let
(
:label
)
{
create
(
:label
,
project:
project
)
}
it
'sorts by most recently updated'
do
baz
.
updated_at
=
Time
.
now
+
100
baz
.
save
visit
project_issues_path
(
project
,
sort:
sort_value_recently_updated
)
expect
(
first_issue
).
to
include
(
'baz'
)
end
describe
'sorting by due date'
do
before
do
create
(
:label_link
,
label:
label
,
target:
foo
)
foo
.
update
(
due_date:
1
.
day
.
from_now
)
bar
.
update
(
due_date:
6
.
days
.
from_now
)
end
it
'sorts by least recently due date by excluding nil due dates'
do
it
'sorts by due date'
do
visit
project_issues_path
(
project
,
sort:
sort_value_due_date
)
expect
(
first_issue
).
to
include
(
'foo'
)
end
it
'sorts by due date by excluding nil due dates'
do
bar
.
update
(
due_date:
nil
)
visit
project_issues_path
(
project
,
label_names:
[
label
.
name
],
sort:
sort_value_due_date_later
)
visit
project_issues_path
(
project
,
sort:
sort_value_due_date
)
expect
(
first_issue
).
to
include
(
'foo'
)
end
end
end
describe
'filtering by due date'
do
before
do
foo
.
update
(
due_date:
1
.
day
.
from_now
)
bar
.
update
(
due_date:
6
.
days
.
from_now
)
end
context
'with a filter on labels'
do
let
(
:label
)
{
create
(
:label
,
project:
project
)
}
before
do
create
(
:label_link
,
label:
label
,
target:
foo
)
end
it
'sorts by least recently due date by excluding nil due dates'
do
bar
.
update
(
due_date:
nil
)
it
'filters by none'
do
visit
project_issues_path
(
project
,
due_date:
Issue
::
NoDueDate
.
name
)
visit
project_issues_path
(
project
,
label_names:
[
label
.
name
],
sort:
sort_value_due_date_later
)
page
.
within
'.issues-holder'
do
expect
(
page
).
not_to
have_content
(
'foo'
)
expect
(
page
).
not_to
have_content
(
'bar'
)
expect
(
page
).
to
have_content
(
'baz'
)
expect
(
first_issue
).
to
include
(
'foo'
)
end
end
end
it
'filters by any'
do
visit
project_issues_path
(
project
,
due_date:
Issue
::
AnyDueDate
.
name
)
describe
'filtering by due date'
do
before
do
foo
.
update
(
due_date:
1
.
day
.
from_now
)
bar
.
update
(
due_date:
6
.
days
.
from_now
)
end
it
'filters by none'
do
visit
project_issues_path
(
project
,
due_date:
Issue
::
NoDueDate
.
name
)
page
.
within
'.issues-holder'
do
expect
(
page
).
to
have_content
(
'foo'
)
expect
(
page
).
to
have_content
(
'bar'
)
expect
(
page
).
to
have_content
(
'baz'
)
page
.
within
'.issues-holder'
do
expect
(
page
).
not_to
have_content
(
'foo'
)
expect
(
page
).
not_to
have_content
(
'bar'
)
expect
(
page
).
to
have_content
(
'baz'
)
end
end
it
'filters by any'
do
visit
project_issues_path
(
project
,
due_date:
Issue
::
AnyDueDate
.
name
)
page
.
within
'.issues-holder'
do
expect
(
page
).
to
have_content
(
'foo'
)
expect
(
page
).
to
have_content
(
'bar'
)
expect
(
page
).
to
have_content
(
'baz'
)
end
end
end
it
'filters by due this week'
do
foo
.
update
(
due_date:
Date
.
today
.
beginning_of_week
+
2
.
days
)
bar
.
update
(
due_date:
Date
.
today
.
end_of_week
)
baz
.
update
(
due_date:
Date
.
today
-
8
.
days
)
it
'filters by due this week'
do
foo
.
update
(
due_date:
Date
.
today
.
beginning_of_week
+
2
.
days
)
bar
.
update
(
due_date:
Date
.
today
.
end_of_week
)
baz
.
update
(
due_date:
Date
.
today
-
8
.
days
)
visit
project_issues_path
(
project
,
due_date:
Issue
::
DueThisWeek
.
name
)
visit
project_issues_path
(
project
,
due_date:
Issue
::
DueThisWeek
.
name
)
page
.
within
'.issues-holder'
do
expect
(
page
).
to
have_content
(
'foo'
)
expect
(
page
).
to
have_content
(
'bar'
)
expect
(
page
).
not_to
have_content
(
'baz'
)
page
.
within
'.issues-holder'
do
expect
(
page
).
to
have_content
(
'foo'
)
expect
(
page
).
to
have_content
(
'bar'
)
expect
(
page
).
not_to
have_content
(
'baz'
)
end
end
end
it
'filters by due this month'
do
foo
.
update
(
due_date:
Date
.
today
.
beginning_of_month
+
2
.
days
)
bar
.
update
(
due_date:
Date
.
today
.
end_of_month
)
baz
.
update
(
due_date:
Date
.
today
-
50
.
days
)
it
'filters by due this month'
do
foo
.
update
(
due_date:
Date
.
today
.
beginning_of_month
+
2
.
days
)
bar
.
update
(
due_date:
Date
.
today
.
end_of_month
)
baz
.
update
(
due_date:
Date
.
today
-
50
.
days
)
visit
project_issues_path
(
project
,
due_date:
Issue
::
DueThisMonth
.
name
)
visit
project_issues_path
(
project
,
due_date:
Issue
::
DueThisMonth
.
name
)
page
.
within
'.issues-holder'
do
expect
(
page
).
to
have_content
(
'foo'
)
expect
(
page
).
to
have_content
(
'bar'
)
expect
(
page
).
not_to
have_content
(
'baz'
)
page
.
within
'.issues-holder'
do
expect
(
page
).
to
have_content
(
'foo'
)
expect
(
page
).
to
have_content
(
'bar'
)
expect
(
page
).
not_to
have_content
(
'baz'
)
end
end
end
it
'filters by overdue'
do
foo
.
update
(
due_date:
Date
.
today
+
2
.
days
)
bar
.
update
(
due_date:
Date
.
today
+
20
.
days
)
baz
.
update
(
due_date:
Date
.
yesterday
)
it
'filters by overdue'
do
foo
.
update
(
due_date:
Date
.
today
+
2
.
days
)
bar
.
update
(
due_date:
Date
.
today
+
20
.
days
)
baz
.
update
(
due_date:
Date
.
yesterday
)
visit
project_issues_path
(
project
,
due_date:
Issue
::
Overdue
.
name
)
visit
project_issues_path
(
project
,
due_date:
Issue
::
Overdue
.
name
)
page
.
within
'.issues-holder'
do
expect
(
page
).
not_to
have_content
(
'foo'
)
expect
(
page
).
not_to
have_content
(
'bar'
)
expect
(
page
).
to
have_content
(
'baz'
)
page
.
within
'.issues-holder'
do
expect
(
page
).
not_to
have_content
(
'foo'
)
expect
(
page
).
not_to
have_content
(
'bar'
)
expect
(
page
).
to
have_content
(
'baz'
)
end
end
end
end
describe
'sorting by milestone'
do
before
do
foo
.
milestone
=
newer_due_milestone
foo
.
save
bar
.
milestone
=
later_due_milestone
bar
.
save
end
describe
'sorting by milestone'
do
before
do
foo
.
milestone
=
newer_due_milestone
foo
.
save
bar
.
milestone
=
later_due_milestone
bar
.
save
end
it
'sorts by milestone'
do
visit
project_issues_path
(
project
,
sort:
sort_value_milestone
)
it
'sorts by milestone'
do
visit
project_issues_path
(
project
,
sort:
sort_value_milestone
)
expect
(
first_issue
).
to
include
(
'foo'
)
expect
(
last_issue
).
to
include
(
'baz'
)
expect
(
first_issue
).
to
include
(
'foo'
)
expect
(
last_issue
).
to
include
(
'baz'
)
end
end
end
describe
'combine filter and sort'
do
let
(
:user2
)
{
create
(
:user
)
}
describe
'combine filter and sort'
do
let
(
:user2
)
{
create
(
:user
)
}
before
do
foo
.
assignees
<<
user2
foo
.
save
bar
.
assignees
<<
user2
bar
.
save
end
before
do
foo
.
assignees
<<
user2
foo
.
save
bar
.
assignees
<<
user2
bar
.
save
end
it
'sorts with a filter applied'
do
visit
project_issues_path
(
project
,
sort:
sort_value_created_date
,
assignee_id:
user2
.
id
)
it
'sorts with a filter applied'
do
visit
project_issues_path
(
project
,
sort:
sort_value_created_date
,
assignee_id:
user2
.
id
)
expect
(
first_issue
).
to
include
(
'foo'
)
expect
(
last_issue
).
to
include
(
'bar'
)
expect
(
page
).
not_to
have_content
(
'baz'
)
expect
(
first_issue
).
to
include
(
'foo'
)
expect
(
last_issue
).
to
include
(
'bar'
)
expect
(
page
).
not_to
have_content
(
'baz'
)
end
end
end
end
describe
'when I want to reset my incoming email token'
do
let
(
:project1
)
{
create
(
:project
,
namespace:
user
.
namespace
)
}
let!
(
:issue
)
{
create
(
:issue
,
project:
project1
)
}
describe
'when I want to reset my incoming email token'
do
let
(
:project1
)
{
create
(
:project
,
namespace:
user
.
namespace
)
}
let!
(
:issue
)
{
create
(
:issue
,
project:
project1
)
}
before
do
stub_incoming_email_setting
(
enabled:
true
,
address:
"p+%{key}@gl.ab"
)
project1
.
team
<<
[
user
,
:master
]
visit
namespace_project_issues_path
(
user
.
namespace
,
project1
)
end
before
do
stub_incoming_email_setting
(
enabled:
true
,
address:
"p+%{key}@gl.ab"
)
project1
.
team
<<
[
user
,
:master
]
visit
namespace_project_issues_path
(
user
.
namespace
,
project1
)
end
it
'changes incoming email address token'
,
:js
do
find
(
'.issuable-email-modal-btn'
).
click
previous_token
=
find
(
'input#issuable_email'
).
value
find
(
'.incoming-email-token-reset'
).
click
it
'changes incoming email address token'
,
:js
do
find
(
'.issuable-email-modal-btn'
).
click
previous_token
=
find
(
'input#issuable_email'
).
value
find
(
'.incoming-email-token-reset'
).
click
wait_for_requests
wait_for_requests
expect
(
page
).
to
have_no_field
(
'issuable_email'
,
with:
previous_token
)
new_token
=
project1
.
new_issuable_address
(
user
.
reload
,
'issue'
)
expect
(
page
).
to
have_field
(
'issuable_email'
,
with:
new_token
)
expect
(
page
).
to
have_no_field
(
'issuable_email'
,
with:
previous_token
)
new_token
=
project1
.
new_issuable_address
(
user
.
reload
,
'issue'
)
expect
(
page
).
to
have_field
(
'issuable_email'
,
with:
new_token
)
end
end
end
describe
'update labels from issue#show'
,
:js
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
,
assignees:
[
user
])
}
let!
(
:label
)
{
create
(
:label
,
project:
project
)
}
describe
'update labels from issue#show'
,
:js
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
,
assignees:
[
user
])
}
let!
(
:label
)
{
create
(
:label
,
project:
project
)
}
before
do
visit
project_issue_path
(
project
,
issue
)
end
before
do
visit
project_issue_path
(
project
,
issue
)
end
it
'will not send ajax request when no data is changed'
do
page
.
within
'.labels'
do
click_link
'Edit'
it
'will not send ajax request when no data is changed'
do
page
.
within
'.labels'
do
click_link
'Edit'
find
(
'.dropdown-menu-close'
,
match: :first
).
click
find
(
'.dropdown-menu-close'
,
match: :first
).
click
expect
(
page
).
not_to
have_selector
(
'.block-loading'
)
expect
(
page
).
not_to
have_selector
(
'.block-loading'
)
end
end
end
end
describe
'update assignee from issue#show'
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
,
assignees:
[
user
])
}
describe
'update assignee from issue#show'
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
,
assignees:
[
user
])
}
context
'by authorized user'
do
it
'allows user to select unassigned'
,
:js
do
visit
project_issue_path
(
project
,
issue
)
context
'by authorized user'
do
it
'allows user to select unassigned'
,
:js
do
visit
project_issue_path
(
project
,
issue
)
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
"
#{
user
.
name
}
"
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
"
#{
user
.
name
}
"
click_link
'Edit'
click_link
'Unassigned'
first
(
'.title'
).
click
expect
(
page
).
to
have_content
'No assignee'
end
click_link
'Edit'
click_link
'Unassigned'
first
(
'.title'
).
click
expect
(
page
).
to
have_content
'No assignee'
end
# wait_for_requests does not work with vue-resource at the moment
sleep
1
# wait_for_requests does not work with vue-resource at the moment
sleep
1
expect
(
issue
.
reload
.
assignees
).
to
be_empty
end
expect
(
issue
.
reload
.
assignees
).
to
be_empty
end
it
'allows user to select an assignee'
,
:js
do
issue2
=
create
(
:issue
,
project:
project
,
author:
user
)
visit
project_issue_path
(
project
,
issue2
)
it
'allows user to select an assignee'
,
:js
do
issue2
=
create
(
:issue
,
project:
project
,
author:
user
)
visit
project_issue_path
(
project
,
issue2
)
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
"No assignee"
end
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
"No assignee"
end
page
.
within
'.assignee'
do
click_link
'Edit'
end
page
.
within
'.assignee'
do
click_link
'Edit'
end
page
.
within
'.dropdown-menu-user'
do
click_link
user
.
name
end
page
.
within
'.dropdown-menu-user'
do
click_link
user
.
name
end
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
user
.
name
page
.
within
(
'.assignee'
)
do
expect
(
page
).
to
have_content
user
.
name
end
end
end
it
'allows user to unselect themselves'
,
:js
do
issue2
=
create
(
:issue
,
project:
project
,
author:
user
)
visit
project_issue_path
(
project
,
issue2
)
it
'allows user to unselect themselves'
,
:js
do
issue2
=
create
(
:issue
,
project:
project
,
author:
user
)
visit
project_issue_path
(
project
,
issue2
)
page
.
within
'.assignee'
do
click_link
'Edit'
click_link
user
.
name
page
.
within
'.assignee'
do
click_link
'Edit'
click_link
user
.
name
find
(
'.dropdown-menu-toggle'
).
click
find
(
'.dropdown-menu-toggle'
).
click
page
.
within
'.value .author'
do
expect
(
page
).
to
have_content
user
.
name
end
page
.
within
'.value .author'
do
expect
(
page
).
to
have_content
user
.
name
end
click_link
'Edit'
click_link
user
.
name
click_link
'Edit'
click_link
user
.
name
find
(
'.dropdown-menu-toggle'
).
click
find
(
'.dropdown-menu-toggle'
).
click
page
.
within
'.value .assign-yourself'
do
expect
(
page
).
to
have_content
"No assignee"
page
.
within
'.value .assign-yourself'
do
expect
(
page
).
to
have_content
"No assignee"
end
end
end
end
end
context
'by unauthorized user'
do
let
(
:guest
)
{
create
(
:user
)
}
context
'by unauthorized user'
do
let
(
:guest
)
{
create
(
:user
)
}
before
do
project
.
team
<<
[[
guest
],
:guest
]
end
before
do
project
.
team
<<
[[
guest
],
:guest
]
end
it
'shows assignee text'
,
:js
do
sign_out
(
:user
)
sign_in
(
guest
)
it
'shows assignee text'
,
:js
do
sign_out
(
:user
)
sign_in
(
guest
)
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
issue
.
assignees
.
first
.
name
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
issue
.
assignees
.
first
.
name
end
end
end
end
describe
'update weight from issue#show'
,
:js
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
describe
'update weight from issue#show'
,
:js
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
before
do
visit
project_issue_path
(
project
,
issue
)
end
before
do
visit
project_issue_path
(
project
,
issue
)
end
it
'allows user to update to a weight'
do
page
.
within
(
'.weight'
)
do
expect
(
page
).
to
have_content
"None"
click_link
'Edit'
it
'allows user to update to a weight'
do
page
.
within
(
'.weight'
)
do
expect
(
page
).
to
have_content
"None"
click_link
'Edit'
find
(
'.dropdown-content a'
,
text:
'1'
).
click
find
(
'.dropdown-content a'
,
text:
'1'
).
click
page
.
within
(
'.value'
)
do
expect
(
page
).
to
have_content
"1"
page
.
within
(
'.value'
)
do
expect
(
page
).
to
have_content
"1"
end
end
end
end
end
describe
'update milestone from issue#show'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
let!
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
describe
'update milestone from issue#show'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
let!
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
context
'by authorized user'
do
it
'allows user to select unassigned'
,
:js
do
visit
project_issue_path
(
project
,
issue
)
context
'by authorized user'
do
it
'allows user to select unassigned'
,
:js
do
visit
project_issue_path
(
project
,
issue
)
page
.
within
(
'.milestone'
)
do
expect
(
page
).
to
have_content
"None"
end
page
.
within
(
'.milestone'
)
do
expect
(
page
).
to
have_content
"None"
end
find
(
'.block.milestone .edit-link'
).
click
sleep
2
# wait for ajax stuff to complete
first
(
'.dropdown-content li'
).
click
sleep
2
page
.
within
(
'.milestone'
)
do
expect
(
page
).
to
have_content
'None'
end
find
(
'.block.milestone .edit-link'
).
click
sleep
2
# wait for ajax stuff to complete
first
(
'.dropdown-content li'
).
click
sleep
2
page
.
within
(
'.milestone'
)
do
expect
(
page
).
to
have_content
'None'
expect
(
issue
.
reload
.
milestone
).
to
be_nil
end
expect
(
issue
.
reload
.
milestone
).
to
be_nil
end
it
'allows user to de-select milestone'
,
:js
do
visit
project_issue_path
(
project
,
issue
)
it
'allows user to de-select milestone'
,
:js
do
visit
project_issue_path
(
project
,
issue
)
page
.
within
(
'.milestone'
)
do
click_link
'Edit'
click_link
milestone
.
title
page
.
within
(
'.milestone'
)
do
click_link
'Edit'
click_link
milestone
.
title
page
.
within
'.value'
do
expect
(
page
).
to
have_content
milestone
.
title
end
page
.
within
'.value'
do
expect
(
page
).
to
have_content
milestone
.
title
end
click_link
'Edit'
click_link
milestone
.
title
click_link
'Edit'
click_link
milestone
.
title
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'None'
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'None'
end
end
end
end
end
context
'by unauthorized user'
do
let
(
:guest
)
{
create
(
:user
)
}
context
'by unauthorized user'
do
let
(
:guest
)
{
create
(
:user
)
}
before
do
project
.
team
<<
[
guest
,
:guest
]
issue
.
milestone
=
milestone
issue
.
save
end
before
do
project
.
team
<<
[
guest
,
:guest
]
issue
.
milestone
=
milestone
issue
.
save
end
it
'shows milestone text'
,
:js
do
sign_out
(
:user
)
sign_in
(
guest
)
it
'shows milestone text'
,
:js
do
sign_out
(
:user
)
sign_in
(
guest
)
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
milestone
.
title
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
milestone
.
title
end
end
end
end
describe
'new issue'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
describe
'new issue'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
context
'by unauthenticated user'
do
before
do
sign_out
(
:user
)
end
context
'by unauthenticated user'
do
before
do
sign_out
(
:user
)
end
it
'redirects to signin then back to new issue after signin'
do
visit
project_issues_path
(
project
)
it
'redirects to signin then back to new issue after signin'
do
visit
project_issues_path
(
project
)
page
.
within
'.nav-controls'
do
click_link
'New issue'
end
page
.
within
'.nav-controls'
do
click_link
'New issue'
end
expect
(
current_path
).
to
eq
new_user_session_path
expect
(
current_path
).
to
eq
new_user_session_path
gitlab_sign_in
(
create
(
:user
))
gitlab_sign_in
(
create
(
:user
))
expect
(
current_path
).
to
eq
new_project_issue_path
(
project
)
expect
(
current_path
).
to
eq
new_project_issue_path
(
project
)
end
end
end
context
'dropzone upload file'
,
:js
do
before
do
visit
new_project_issue_path
(
project
)
end
context
'dropzone upload file'
,
:js
do
before
do
visit
new_project_issue_path
(
project
)
end
it
'uploads file when dragging into textarea'
do
dropzone_file
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'banana_sample.gif'
)
it
'uploads file when dragging into textarea'
do
dropzone_file
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'banana_sample.gif'
)
expect
(
page
.
find_field
(
"issue_description"
).
value
).
to
have_content
'banana_sample'
end
expect
(
page
.
find_field
(
"issue_description"
).
value
).
to
have_content
'banana_sample'
end
it
"doesn't add double newline to end of a single attachment markdown"
do
dropzone_file
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'banana_sample.gif'
)
it
"doesn't add double newline to end of a single attachment markdown"
do
dropzone_file
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'banana_sample.gif'
)
expect
(
page
.
find_field
(
"issue_description"
).
value
).
not_to
match
/\n\n$/
end
expect
(
page
.
find_field
(
"issue_description"
).
value
).
not_to
match
/\n\n$/
end
it
"cancels a file upload correctly"
do
slow_requests
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)],
0
,
false
)
it
"cancels a file upload correctly"
do
slow_requests
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)],
0
,
false
)
click_button
'Cancel'
end
click_button
'Cancel'
end
expect
(
page
).
to
have_button
(
'Attach a file'
)
expect
(
page
).
not_to
have_button
(
'Cancel'
)
expect
(
page
).
not_to
have_selector
(
'.uploading-progress-container'
,
visible:
true
)
expect
(
page
).
to
have_button
(
'Attach a file'
)
expect
(
page
).
not_to
have_button
(
'Cancel'
)
expect
(
page
).
not_to
have_selector
(
'.uploading-progress-container'
,
visible:
true
)
end
end
end
context
'form filled by URL parameters'
do
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
before
do
project
.
repository
.
create_file
(
user
,
'.gitlab/issue_templates/bug.md'
,
'this is a test "bug" template'
,
message:
'added issue template'
,
branch_name:
'master'
)
context
'form filled by URL parameters'
do
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
visit
new_project_issue_path
(
project
,
issuable_template:
'bug'
)
end
before
do
project
.
repository
.
create_file
(
user
,
'.gitlab/issue_templates/bug.md'
,
'this is a test "bug" template'
,
message:
'added issue template'
,
branch_name:
'master'
)
visit
new_project_issue_path
(
project
,
issuable_template:
'bug'
)
end
it
'fills in template'
do
expect
(
find
(
'.js-issuable-selector .dropdown-toggle-text'
)).
to
have_content
(
'bug'
)
it
'fills in template'
do
expect
(
find
(
'.js-issuable-selector .dropdown-toggle-text'
)).
to
have_content
(
'bug'
)
end
end
end
end
describe
'new issue by email'
do
shared_examples
'show the email in the modal'
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
describe
'new issue by email'
do
shared_examples
'show the email in the modal'
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
before
do
project
.
issues
<<
issue
stub_incoming_email_setting
(
enabled:
true
,
address:
"p+%{key}@gl.ab"
)
before
do
project
.
issues
<<
issue
stub_incoming_email_setting
(
enabled:
true
,
address:
"p+%{key}@gl.ab"
)
visit
project_issues_path
(
project
)
click_button
(
'Email a new issue'
)
end
visit
project_issues_path
(
project
)
click_button
(
'Email a new issue'
)
end
it
'click the button to show modal for the new email'
do
page
.
within
'#issuable-email-modal'
do
email
=
project
.
new_issuable_address
(
user
,
'issue'
)
it
'click the button to show modal for the new email'
do
page
.
within
'#issuable-email-modal'
do
email
=
project
.
new_issuable_address
(
user
,
'issue'
)
expect
(
page
).
to
have_selector
(
"input[value='
#{
email
}
']"
)
expect
(
page
).
to
have_selector
(
"input[value='
#{
email
}
']"
)
end
end
end
end
context
'with existing issues'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
context
'with existing issues'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
it_behaves_like
'show the email in the modal'
end
it_behaves_like
'show the email in the modal'
end
context
'without existing issues'
do
it_behaves_like
'show the email in the modal'
context
'without existing issues'
do
it_behaves_like
'show the email in the modal'
end
end
end
describe
'due date'
do
context
'update due on issue#show'
,
:js
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
,
assignees:
[
user
])
}
describe
'due date'
do
context
'update due on issue#show'
,
:js
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
,
assignees:
[
user
])
}
before
do
visit
project_issue_path
(
project
,
issue
)
end
before
do
visit
project_issue_path
(
project
,
issue
)
end
it
'adds due date to issue'
do
date
=
Date
.
today
.
at_beginning_of_month
+
2
.
days
it
'adds due date to issue'
do
date
=
Date
.
today
.
at_beginning_of_month
+
2
.
days
page
.
within
'.due_date'
do
click_link
'Edit'
page
.
within
'.due_date'
do
click_link
'Edit'
page
.
within
'.pika-single'
do
click_button
date
.
day
end
page
.
within
'.pika-single'
do
click_button
date
.
day
end
wait_for_requests
wait_for_requests
expect
(
find
(
'.value'
).
text
).
to
have_content
date
.
strftime
(
'%b %-d, %Y'
)
expect
(
find
(
'.value'
).
text
).
to
have_content
date
.
strftime
(
'%b %-d, %Y'
)
end
end
end
it
'removes due date from issue'
do
date
=
Date
.
today
.
at_beginning_of_month
+
2
.
days
it
'removes due date from issue'
do
date
=
Date
.
today
.
at_beginning_of_month
+
2
.
days
page
.
within
'.due_date'
do
click_link
'Edit'
page
.
within
'.due_date'
do
click_link
'Edit'
page
.
within
'.pika-single'
do
click_button
date
.
day
end
page
.
within
'.pika-single'
do
click_button
date
.
day
end
wait_for_requests
wait_for_requests
expect
(
page
).
to
have_no_content
'No due date'
expect
(
page
).
to
have_no_content
'No due date'
click_link
'remove due date'
expect
(
page
).
to
have_content
'No due date'
click_link
'remove due date'
expect
(
page
).
to
have_content
'No due date'
end
end
end
end
end
describe
'title issue#show'
,
:js
do
it
'updates the title'
,
:js
do
issue
=
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
,
title:
'new title'
)
describe
'title issue#show'
,
:js
do
it
'updates the title'
,
:js
do
issue
=
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project
,
title:
'new title'
)
visit
project_issue_path
(
project
,
issue
)
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_text
(
"new title"
)
expect
(
page
).
to
have_text
(
"new title"
)
issue
.
update
(
title:
"updated title"
)
issue
.
update
(
title:
"updated title"
)
wait_for_requests
expect
(
page
).
to
have_text
(
"updated title"
)
wait_for_requests
expect
(
page
).
to
have_text
(
"updated title"
)
end
end
end
describe
'confidential issue#show'
,
:js
do
it
'shows confidential sibebar information as confidential and can be turned off'
do
issue
=
create
(
:issue
,
:confidential
,
project:
project
)
describe
'confidential issue#show'
,
:js
do
it
'shows confidential sibebar information as confidential and can be turned off'
do
issue
=
create
(
:issue
,
:confidential
,
project:
project
)
visit
project_issue_path
(
project
,
issue
)
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_css
(
'.issuable-note-warning'
)
expect
(
find
(
'.issuable-sidebar-item.confidentiality'
)).
to
have_css
(
'.is-active'
)
expect
(
find
(
'.issuable-sidebar-item.confidentiality'
)).
not_to
have_css
(
'.not-active'
)
expect
(
page
).
to
have_css
(
'.issuable-note-warning'
)
expect
(
find
(
'.issuable-sidebar-item.confidentiality'
)).
to
have_css
(
'.is-active'
)
expect
(
find
(
'.issuable-sidebar-item.confidentiality'
)).
not_to
have_css
(
'.not-active'
)
find
(
'.confidential-edit'
).
click
expect
(
page
).
to
have_css
(
'.sidebar-item-warning-message'
)
find
(
'.confidential-edit'
).
click
expect
(
page
).
to
have_css
(
'.sidebar-item-warning-message'
)
within
(
'.sidebar-item-warning-message'
)
do
find
(
'.btn-close'
).
click
end
within
(
'.sidebar-item-warning-message'
)
do
find
(
'.btn-close'
).
click
end
wait_for_requests
wait_for_requests
visit
project_issue_path
(
project
,
issue
)
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
not_to
have_css
(
'.is-active'
)
expect
(
page
).
not_to
have_css
(
'.is-active'
)
end
end
end
end
spec/features/merge_requests/mini_pipeline_graph_spec.rb
View file @
f8cda25d
...
...
@@ -15,8 +15,8 @@ feature 'Mini Pipeline Graph', :js do
visit_merge_request
end
def
visit_merge_request
(
format
=
:htm
l
)
visit
project_merge_request_path
(
project
,
merge_request
,
format:
format
)
def
visit_merge_request
(
format
: :html
,
serializer:
ni
l
)
visit
project_merge_request_path
(
project
,
merge_request
,
format:
format
,
serializer:
serializer
)
end
it
'should display a mini pipeline graph'
do
...
...
@@ -33,12 +33,12 @@ feature 'Mini Pipeline Graph', :js do
end
it
'avoids repeated database queries'
do
before
=
ActiveRecord
::
QueryRecorder
.
new
{
visit_merge_request
(
:json
)
}
before
=
ActiveRecord
::
QueryRecorder
.
new
{
visit_merge_request
(
format: :json
,
serializer:
'widget'
)
}
create
(
:ci_build
,
pipeline:
pipeline
,
legacy_artifacts_file:
artifacts_file2
)
create
(
:ci_build
,
pipeline:
pipeline
,
when:
'manual'
)
after
=
ActiveRecord
::
QueryRecorder
.
new
{
visit_merge_request
(
:json
)
}
after
=
ActiveRecord
::
QueryRecorder
.
new
{
visit_merge_request
(
format: :json
,
serializer:
'widget'
)
}
expect
(
before
.
count
).
to
eq
(
after
.
count
)
expect
(
before
.
cached_count
).
to
eq
(
after
.
cached_count
)
...
...
spec/fixtures/api/schemas/entities/merge_request.json
→
spec/fixtures/api/schemas/entities/merge_request
_widget
.json
View file @
f8cda25d
...
...
@@ -81,15 +81,15 @@
"target_branch_tree_path"
:
{
"type"
:
"string"
},
"source_branch_path"
:
{
"type"
:
"string"
},
"conflict_resolution_path"
:
{
"type"
:
[
"string"
,
"null"
]
},
"cancel_merge_when_pipeline_succeeds_path"
:
{
"type"
:
"string"
},
"create_issue_to_resolve_discussions_path"
:
{
"type"
:
"string"
},
"merge_path"
:
{
"type"
:
"string"
},
"cancel_merge_when_pipeline_succeeds_path"
:
{
"type"
:
[
"string"
,
"null"
]
},
"create_issue_to_resolve_discussions_path"
:
{
"type"
:
[
"string"
,
"null"
]
},
"merge_path"
:
{
"type"
:
[
"string"
,
"null"
]
},
"cherry_pick_in_fork_path"
:
{
"type"
:
[
"string"
,
"null"
]
},
"revert_in_fork_path"
:
{
"type"
:
[
"string"
,
"null"
]
},
"email_patches_path"
:
{
"type"
:
"string"
},
"plain_diff_path"
:
{
"type"
:
"string"
},
"status_path"
:
{
"type"
:
"string"
},
"new_blob_path"
:
{
"type"
:
"string"
},
"new_blob_path"
:
{
"type"
:
[
"string"
,
"null"
]
},
"merge_check_path"
:
{
"type"
:
"string"
},
"ci_environments_status_path"
:
{
"type"
:
"string"
},
"merge_commit_message_with_description"
:
{
"type"
:
"string"
},
...
...
spec/javascripts/collapsed_sidebar_todo_spec.js
View file @
f8cda25d
/* global Sidebar */
/* eslint-disable no-new */
import
_
from
'
underscore
'
;
import
'
~/right_sidebar
'
;
import
Sidebar
from
'
~/right_sidebar
'
;
describe
(
'
Issuable right sidebar collapsed todo toggle
'
,
()
=>
{
const
fixtureName
=
'
issues/open-issue.html.raw
'
;
...
...
spec/javascripts/line_highlighter_spec.js
View file @
f8cda25d
/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */
/* global LineHighlighter */
import
'
~/line_highlighter
'
;
import
LineHighlighter
from
'
~/line_highlighter
'
;
(
function
()
{
describe
(
'
LineHighlighter
'
,
function
()
{
...
...
spec/javascripts/merge_request_notes_spec.js
View file @
f8cda25d
/* global Notes */
import
'
autosize
'
;
import
'
~/gl_form
'
;
import
'
~/lib/utils/text_utility
'
;
import
'
~/render_gfm
'
;
import
'
~/render_math
'
;
import
'
~/notes
'
;
import
Notes
from
'
~/notes
'
;
const
upArrowKeyCode
=
38
;
...
...
spec/javascripts/merge_request_spec.js
View file @
f8cda25d
/* eslint-disable space-before-function-paren, no-return-assign */
/* global MergeRequest */
import
'
~/merge_request
'
;
import
MergeRequest
from
'
~/merge_request
'
;
import
CloseReopenReportToggle
from
'
~/close_reopen_report_toggle
'
;
import
IssuablesHelper
from
'
~/helpers/issuables_helper
'
;
...
...
spec/javascripts/merge_request_tabs_spec.js
View file @
f8cda25d
/* eslint-disable no-var, comma-dangle, object-shorthand */
/* global Notes */
import
*
as
urlUtils
from
'
~/lib/utils/url_utility
'
;
import
'
~/merge_request_tabs
'
;
...
...
@@ -7,7 +6,7 @@ import '~/commit/pipelines/pipelines_bundle';
import
'
~/breakpoints
'
;
import
'
~/lib/utils/common_utils
'
;
import
Diff
from
'
~/diff
'
;
import
'
~/notes
'
;
import
Notes
from
'
~/notes
'
;
import
'
vendor/jquery.scrollTo
'
;
(
function
()
{
...
...
@@ -279,8 +278,8 @@ import 'vendor/jquery.scrollTo';
loadFixtures
(
'
merge_requests/diff_comment.html.raw
'
);
$
(
'
body
'
).
attr
(
'
data-page
'
,
'
projects:merge_requests:show
'
);
window
.
gl
.
ImageFile
=
()
=>
{};
window
.
notes
=
new
Notes
(
''
,
[]);
spyOn
(
window
.
notes
,
'
toggleDiffNote
'
).
and
.
callThrough
();
Notes
.
initialize
(
''
,
[]);
spyOn
(
Notes
.
instance
,
'
toggleDiffNote
'
).
and
.
callThrough
();
});
afterEach
(()
=>
{
...
...
@@ -338,7 +337,7 @@ import 'vendor/jquery.scrollTo';
this
.
class
.
loadDiff
(
'
/foo/bar/merge_requests/1/diffs
'
);
expect
(
noteId
.
length
).
toBeGreaterThan
(
0
);
expect
(
window
.
notes
.
toggleDiffNote
).
toHaveBeenCalledWith
({
expect
(
Notes
.
instance
.
toggleDiffNote
).
toHaveBeenCalledWith
({
target
:
jasmine
.
any
(
Object
),
lineType
:
'
old
'
,
forceShow
:
true
,
...
...
@@ -349,7 +348,7 @@ import 'vendor/jquery.scrollTo';
spyOn
(
urlUtils
,
'
getLocationHash
'
).
and
.
returnValue
(
'
note_something-that-does-not-exist
'
);
this
.
class
.
loadDiff
(
'
/foo/bar/merge_requests/1/diffs
'
);
expect
(
window
.
notes
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
expect
(
Notes
.
instance
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
});
});
...
...
@@ -359,7 +358,7 @@ import 'vendor/jquery.scrollTo';
this
.
class
.
loadDiff
(
'
/foo/bar/merge_requests/1/diffs
'
);
expect
(
noteLineNumId
.
length
).
toBeGreaterThan
(
0
);
expect
(
window
.
notes
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
expect
(
Notes
.
instance
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
});
});
});
...
...
@@ -393,7 +392,7 @@ import 'vendor/jquery.scrollTo';
this
.
class
.
loadDiff
(
'
/foo/bar/merge_requests/1/diffs
'
);
expect
(
noteId
.
length
).
toBeGreaterThan
(
0
);
expect
(
window
.
notes
.
toggleDiffNote
).
toHaveBeenCalledWith
({
expect
(
Notes
.
instance
.
toggleDiffNote
).
toHaveBeenCalledWith
({
target
:
jasmine
.
any
(
Object
),
lineType
:
'
new
'
,
forceShow
:
true
,
...
...
@@ -404,7 +403,7 @@ import 'vendor/jquery.scrollTo';
spyOn
(
urlUtils
,
'
getLocationHash
'
).
and
.
returnValue
(
'
note_something-that-does-not-exist
'
);
this
.
class
.
loadDiff
(
'
/foo/bar/merge_requests/1/diffs
'
);
expect
(
window
.
notes
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
expect
(
Notes
.
instance
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
});
});
...
...
@@ -414,7 +413,7 @@ import 'vendor/jquery.scrollTo';
this
.
class
.
loadDiff
(
'
/foo/bar/merge_requests/1/diffs
'
);
expect
(
noteLineNumId
.
length
).
toBeGreaterThan
(
0
);
expect
(
window
.
notes
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
expect
(
Notes
.
instance
.
toggleDiffNote
).
not
.
toHaveBeenCalled
();
});
});
});
...
...
spec/javascripts/notes_spec.js
View file @
f8cda25d
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
/* global Notes */
import
*
as
urlUtils
from
'
~/lib/utils/url_utility
'
;
import
'
autosize
'
;
import
'
~/gl_form
'
;
import
'
~/lib/utils/text_utility
'
;
import
'
~/render_gfm
'
;
import
'
~/notes
'
;
import
Notes
from
'
~/notes
'
;
(
function
()
{
window
.
gon
||
(
window
.
gon
=
{});
...
...
spec/javascripts/right_sidebar_spec.js
View file @
f8cda25d
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */
/* global Sidebar */
import
'
~/commons/bootstrap
'
;
import
'
~/right_sidebar
'
;
import
Sidebar
from
'
~/right_sidebar
'
;
(
function
()
{
var
$aside
,
$icon
,
$labelsIcon
,
$page
,
$toggle
,
assertSidebarState
;
...
...
spec/serializers/merge_request_serializer_spec.rb
View file @
f8cda25d
require
'spec_helper'
describe
MergeRequestSerializer
do
let
(
:user
)
{
build_stubbed
(
:user
)
}
let
(
:merge_request
)
{
build_stubbed
(
:merge_request
)
}
let
(
:serializer
)
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:resource
)
{
create
(
:merge_request
)
}
let
(
:json_entity
)
do
described_class
.
new
(
current_user:
user
)
.
represent
(
resource
,
serializer:
serializer
)
.
with_indifferent_access
end
describe
'#represent'
do
let
(
:opts
)
{
{
serializer:
serializer_entity
}
}
subject
{
serializer
.
represent
(
merge_request
,
serializer:
serializer_entity
)
}
context
'widget merge request serialization'
do
let
(
:serializer
)
{
'widget'
}
context
'when passing basic serializer param'
do
let
(
:serializer_entity
)
{
'basic'
}
it
'matches issue json schema'
do
expect
(
json_entity
).
to
match_schema
(
'entities/merge_request_widget'
)
end
end
it
'calls super class #represent with correct params'
do
expect_any_instance_of
(
BaseSerializer
).
to
receive
(
:represent
)
.
with
(
merge_request
,
opts
,
MergeRequestBasicEntity
)
context
'sidebar merge request serialization'
do
let
(
:serializer
)
{
'sidebar'
}
subject
e
nd
it
'matches basic merge request json schema'
do
e
xpect
(
json_entity
).
to
match_schema
(
'entities/merge_request_basic'
)
end
end
context
'when serializer param is falsy'
do
let
(
:serializer_entity
)
{
nil
}
context
'basic merge request serialization'
do
let
(
:serializer
)
{
'basic'
}
it
'matches basic merge request json schema'
do
expect
(
json_entity
).
to
match_schema
(
'entities/merge_request_basic'
)
end
end
it
'calls super class #represent with correct params'
do
expect_any_instance_of
(
BaseSerializer
).
to
receive
(
:represent
)
.
with
(
merge_request
,
opts
,
MergeRequestEntity
)
context
'no serializer'
do
let
(
:serializer
)
{
nil
}
subject
e
nd
it
'raises an error'
do
e
xpect
{
json_entity
}.
to
raise_error
(
NoMethodError
)
end
end
end
spec/serializers/merge_request_entity_spec.rb
→
spec/serializers/merge_request_
widget_
entity_spec.rb
View file @
f8cda25d
require
'spec_helper'
describe
MergeRequestEntity
do
describe
MergeRequest
Widget
Entity
do
let
(
:project
)
{
create
:project
,
:repository
}
let
(
:resource
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
let
(
:user
)
{
create
(
:user
)
}
...
...
@@ -35,37 +35,6 @@ describe MergeRequestEntity do
end
end
it
'includes issues_links'
do
issues_links
=
subject
[
:issues_links
]
expect
(
issues_links
).
to
include
(
:closing
,
:mentioned_but_not_closing
,
:assign_to_closing
)
end
it
'has Issuable attributes'
do
expect
(
subject
).
to
include
(
:id
,
:iid
,
:author_id
,
:description
,
:lock_version
,
:milestone_id
,
:title
,
:updated_by_id
,
:created_at
,
:updated_at
,
:milestone
,
:labels
)
end
it
'has time estimation attributes'
do
expect
(
subject
).
to
include
(
:time_estimate
,
:total_time_spent
,
:human_time_estimate
,
:human_total_time_spent
)
end
it
'has important MergeRequest attributes'
do
expect
(
subject
).
to
include
(
:state
,
:deleted_at
,
:diff_head_sha
,
:merge_commit_message
,
:has_conflicts
,
:has_ci
,
:merge_path
,
:conflict_resolution_path
,
:cancel_merge_when_pipeline_succeeds_path
,
:create_issue_to_resolve_discussions_path
,
:source_branch_path
,
:target_branch_commits_path
,
:target_branch_tree_path
,
:commits_count
,
:merge_ongoing
,
:ff_only_enabled
,
## EE
:can_push_to_source_branch
,
:approvals_before_merge
,
:squash
,
:rebase_commit_sha
,
:rebase_in_progress
,
:approvals_path
)
end
it
'has email_patches_path'
do
expect
(
subject
[
:email_patches_path
])
.
to
eq
(
"/
#{
resource
.
project
.
full_path
}
/merge_requests/
#{
resource
.
iid
}
.patch"
)
...
...
@@ -120,18 +89,6 @@ describe MergeRequestEntity do
end
end
it
'includes merge_event'
do
create
(
:event
,
:merged
,
author:
user
,
project:
resource
.
project
,
target:
resource
)
expect
(
subject
[
:merge_event
]).
to
include
(
:author
,
:updated_at
)
end
it
'includes closed_event'
do
create
(
:event
,
:closed
,
author:
user
,
project:
resource
.
project
,
target:
resource
)
expect
(
subject
[
:closed_event
]).
to
include
(
:author
,
:updated_at
)
end
describe
'diverged_commits_count'
do
context
'when MR open and its diverging'
do
it
'returns diverged commits count'
do
...
...
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