Commit 6fb59ffb authored by Matija Čupić's avatar Matija Čupić

Merge branch 'master' into 38175-add-domain-field-to-auto-devops-application-setting

parents 62b7c3ba dc325c67
...@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git ...@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git
- gitlab-org - gitlab-org
.default-cache: &default-cache .default-cache: &default-cache
key: "ruby-235-with-yarn" key: "ruby-2.3.6-with-yarn"
paths: paths:
- vendor/ruby - vendor/ruby
- .yarn-cache/ - .yarn-cache/
...@@ -322,73 +322,72 @@ setup-test-env: ...@@ -322,73 +322,72 @@ setup-test-env:
paths: paths:
- tmp/tests - tmp/tests
rspec-pg 0 26: *rspec-metadata-pg rspec-pg 0 27: *rspec-metadata-pg
rspec-pg 1 26: *rspec-metadata-pg rspec-pg 1 27: *rspec-metadata-pg
rspec-pg 2 26: *rspec-metadata-pg rspec-pg 2 27: *rspec-metadata-pg
rspec-pg 3 26: *rspec-metadata-pg rspec-pg 3 27: *rspec-metadata-pg
rspec-pg 4 26: *rspec-metadata-pg rspec-pg 4 27: *rspec-metadata-pg
rspec-pg 5 26: *rspec-metadata-pg rspec-pg 5 27: *rspec-metadata-pg
rspec-pg 6 26: *rspec-metadata-pg rspec-pg 6 27: *rspec-metadata-pg
rspec-pg 7 26: *rspec-metadata-pg rspec-pg 7 27: *rspec-metadata-pg
rspec-pg 8 26: *rspec-metadata-pg rspec-pg 8 27: *rspec-metadata-pg
rspec-pg 9 26: *rspec-metadata-pg rspec-pg 9 27: *rspec-metadata-pg
rspec-pg 10 26: *rspec-metadata-pg rspec-pg 10 27: *rspec-metadata-pg
rspec-pg 11 26: *rspec-metadata-pg rspec-pg 11 27: *rspec-metadata-pg
rspec-pg 12 26: *rspec-metadata-pg rspec-pg 12 27: *rspec-metadata-pg
rspec-pg 13 26: *rspec-metadata-pg rspec-pg 13 27: *rspec-metadata-pg
rspec-pg 14 26: *rspec-metadata-pg rspec-pg 14 27: *rspec-metadata-pg
rspec-pg 15 26: *rspec-metadata-pg rspec-pg 15 27: *rspec-metadata-pg
rspec-pg 16 26: *rspec-metadata-pg rspec-pg 16 27: *rspec-metadata-pg
rspec-pg 17 26: *rspec-metadata-pg rspec-pg 17 27: *rspec-metadata-pg
rspec-pg 18 26: *rspec-metadata-pg rspec-pg 18 27: *rspec-metadata-pg
rspec-pg 19 26: *rspec-metadata-pg rspec-pg 19 27: *rspec-metadata-pg
rspec-pg 20 26: *rspec-metadata-pg rspec-pg 20 27: *rspec-metadata-pg
rspec-pg 21 26: *rspec-metadata-pg rspec-pg 21 27: *rspec-metadata-pg
rspec-pg 22 26: *rspec-metadata-pg rspec-pg 22 27: *rspec-metadata-pg
rspec-pg 23 26: *rspec-metadata-pg rspec-pg 23 27: *rspec-metadata-pg
rspec-pg 24 26: *rspec-metadata-pg rspec-pg 24 27: *rspec-metadata-pg
rspec-pg 25 26: *rspec-metadata-pg rspec-pg 25 27: *rspec-metadata-pg
rspec-pg 26 27: *rspec-metadata-pg
rspec-mysql 0 26: *rspec-metadata-mysql
rspec-mysql 1 26: *rspec-metadata-mysql rspec-mysql 0 27: *rspec-metadata-mysql
rspec-mysql 2 26: *rspec-metadata-mysql rspec-mysql 1 27: *rspec-metadata-mysql
rspec-mysql 3 26: *rspec-metadata-mysql rspec-mysql 2 27: *rspec-metadata-mysql
rspec-mysql 4 26: *rspec-metadata-mysql rspec-mysql 3 27: *rspec-metadata-mysql
rspec-mysql 5 26: *rspec-metadata-mysql rspec-mysql 4 27: *rspec-metadata-mysql
rspec-mysql 6 26: *rspec-metadata-mysql rspec-mysql 5 27: *rspec-metadata-mysql
rspec-mysql 7 26: *rspec-metadata-mysql rspec-mysql 6 27: *rspec-metadata-mysql
rspec-mysql 8 26: *rspec-metadata-mysql rspec-mysql 7 27: *rspec-metadata-mysql
rspec-mysql 9 26: *rspec-metadata-mysql rspec-mysql 8 27: *rspec-metadata-mysql
rspec-mysql 10 26: *rspec-metadata-mysql rspec-mysql 9 27: *rspec-metadata-mysql
rspec-mysql 11 26: *rspec-metadata-mysql rspec-mysql 10 27: *rspec-metadata-mysql
rspec-mysql 12 26: *rspec-metadata-mysql rspec-mysql 11 27: *rspec-metadata-mysql
rspec-mysql 13 26: *rspec-metadata-mysql rspec-mysql 12 27: *rspec-metadata-mysql
rspec-mysql 14 26: *rspec-metadata-mysql rspec-mysql 13 27: *rspec-metadata-mysql
rspec-mysql 15 26: *rspec-metadata-mysql rspec-mysql 14 27: *rspec-metadata-mysql
rspec-mysql 16 26: *rspec-metadata-mysql rspec-mysql 15 27: *rspec-metadata-mysql
rspec-mysql 17 26: *rspec-metadata-mysql rspec-mysql 16 27: *rspec-metadata-mysql
rspec-mysql 18 26: *rspec-metadata-mysql rspec-mysql 17 27: *rspec-metadata-mysql
rspec-mysql 19 26: *rspec-metadata-mysql rspec-mysql 18 27: *rspec-metadata-mysql
rspec-mysql 20 26: *rspec-metadata-mysql rspec-mysql 19 27: *rspec-metadata-mysql
rspec-mysql 21 26: *rspec-metadata-mysql rspec-mysql 20 27: *rspec-metadata-mysql
rspec-mysql 22 26: *rspec-metadata-mysql rspec-mysql 21 27: *rspec-metadata-mysql
rspec-mysql 23 26: *rspec-metadata-mysql rspec-mysql 22 27: *rspec-metadata-mysql
rspec-mysql 24 26: *rspec-metadata-mysql rspec-mysql 23 27: *rspec-metadata-mysql
rspec-mysql 25 26: *rspec-metadata-mysql rspec-mysql 24 27: *rspec-metadata-mysql
rspec-mysql 25 27: *rspec-metadata-mysql
spinach-pg 0 4: *spinach-metadata-pg rspec-mysql 26 27: *rspec-metadata-mysql
spinach-pg 1 4: *spinach-metadata-pg
spinach-pg 2 4: *spinach-metadata-pg spinach-pg 0 3: *spinach-metadata-pg
spinach-pg 3 4: *spinach-metadata-pg spinach-pg 1 3: *spinach-metadata-pg
spinach-pg 2 3: *spinach-metadata-pg
spinach-mysql 0 4: *spinach-metadata-mysql
spinach-mysql 1 4: *spinach-metadata-mysql spinach-mysql 0 3: *spinach-metadata-mysql
spinach-mysql 2 4: *spinach-metadata-mysql spinach-mysql 1 3: *spinach-metadata-mysql
spinach-mysql 3 4: *spinach-metadata-mysql spinach-mysql 2 3: *spinach-metadata-mysql
# Static analysis jobs # Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis .ruby-static-analysis: &ruby-static-analysis
<<: *pull-cache
variables: variables:
SIMPLECOV: "false" SIMPLECOV: "false"
SETUP_DB: "false" SETUP_DB: "false"
...@@ -409,6 +408,12 @@ static-analysis: ...@@ -409,6 +408,12 @@ static-analysis:
stage: test stage: test
script: script:
- scripts/static-analysis - scripts/static-analysis
cache:
key: "ruby-2.3.6-with-yarn-and-rubocop"
paths:
- vendor/ruby
- .yarn-cache/
- tmp/rubocop_cache
# Documentation checks: # Documentation checks:
# - Check validity of relative links # - Check validity of relative links
...@@ -717,8 +722,6 @@ pages: ...@@ -717,8 +722,6 @@ pages:
cache gems: cache gems:
<<: *dedicated-runner <<: *dedicated-runner
<<: *pull-cache <<: *pull-cache
only:
- tags
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
script: script:
...@@ -729,6 +732,7 @@ cache gems: ...@@ -729,6 +732,7 @@ cache gems:
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee - master@gitlab-org/gitlab-ee
- tags
gitlab_git_test: gitlab_git_test:
<<: *dedicated-runner <<: *dedicated-runner
......
...@@ -17,6 +17,7 @@ AllCops: ...@@ -17,6 +17,7 @@ AllCops:
- 'bin/**/*' - 'bin/**/*'
- 'generator_templates/**/*' - 'generator_templates/**/*'
- 'builds/**/*' - 'builds/**/*'
CacheRootDirectory: tmp
# Gitlab ################################################################### # Gitlab ###################################################################
......
...@@ -342,10 +342,6 @@ RSpec/SharedContext: ...@@ -342,10 +342,6 @@ RSpec/SharedContext:
Exclude: Exclude:
- 'spec/features/admin/admin_groups_spec.rb' - 'spec/features/admin/admin_groups_spec.rb'
# Offense count: 90
RSpec/SingleLineHook:
Enabled: false
# Offense count: 5 # Offense count: 5
RSpec/VoidExpect: RSpec/VoidExpect:
Exclude: Exclude:
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -406,7 +406,7 @@ group :ed25519 do ...@@ -406,7 +406,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.73.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.78.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -285,7 +285,7 @@ GEM ...@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.73.0) gitaly-proto (0.78.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -304,7 +304,7 @@ GEM ...@@ -304,7 +304,7 @@ GEM
mime-types (>= 1.16) mime-types (>= 1.16)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-markup (1.6.3) gitlab-markup (1.6.3)
gitlab-styles (2.3.0) gitlab-styles (2.3.1)
rubocop (~> 0.51) rubocop (~> 0.51)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19) rubocop-rspec (~> 1.19)
...@@ -1056,7 +1056,7 @@ DEPENDENCIES ...@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.73.0) gitaly-proto (~> 0.78.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
......
...@@ -85,7 +85,8 @@ These types of merge requests for the upcoming release need special consideratio ...@@ -85,7 +85,8 @@ These types of merge requests for the upcoming release need special consideratio
and a dedicated team with front-end, back-end, and UX. and a dedicated team with front-end, back-end, and UX.
* **Small features**: any other feature request. * **Small features**: any other feature request.
**Large features** must be with a maintainer **by the 1st**. This means that: It is strongly recommended that **large features** be with a maintainer **by the
1st**. This means that:
* There is a merge request (even if it's WIP). * There is a merge request (even if it's WIP).
* The person (or people, if it needs a frontend and backend maintainer) who will * The person (or people, if it needs a frontend and backend maintainer) who will
...@@ -100,14 +101,37 @@ The maintainer can also choose to assign a reviewer to perform an initial ...@@ -100,14 +101,37 @@ The maintainer can also choose to assign a reviewer to perform an initial
review, but this way the maintainer is unlikely to be surprised by receiving an review, but this way the maintainer is unlikely to be surprised by receiving an
MR later in the cycle. MR later in the cycle.
**Small features** must be with a reviewer (not necessarily maintainer) **by the It is strongly recommended that **small features** be with a reviewer (not
3rd**. necessarily a maintainer) **by the 3rd**.
Most merge requests from the community do not have a specific release Most merge requests from the community do not have a specific release
target. However, if one does and falls into either of the above categories, it's target. However, if one does and falls into either of the above categories, it's
the reviewer's responsibility to manage the above communication and assignment the reviewer's responsibility to manage the above communication and assignment
on behalf of the community member. on behalf of the community member.
#### What happens if these deadlines are missed?
If a small or large feature is _not_ with a maintainer or reviewer by the
recommended date, this does _not_ mean that maintainers or reviewers will refuse
to review or merge it, or that the feature will definitely not make it in before
the feature freeze.
However, with every day that passes without review, it will become more likely
that the feature will slip, because maintainers and reviewers may not have
enough time to do a thorough review, and developers may not have enough time to
adequately address any feedback that may come back.
A maintainer or reviewer may also determine that it will not be possible to
finish the current scope of the feature in time, but that it is possible to
reduce the scope so that something can still ship this month, with the remaining
scope moving to the next release. The sooner this decision is made, in
conversation with the Product Manager and developer, the more time there is to
extract that which is now out of scope, and to finish that which remains in scope.
For these reasons, it is strongly recommended to follow the guidelines above,
to maximize the chances of your feature making it in before the feature freeze,
and to prevent any last minute surprises.
### On the 7th ### On the 7th
Merge requests should still be complete, following the Merge requests should still be complete, following the
......
{"iconCount":189,"spriteSize":85766,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o-open","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]} {"iconCount":189,"spriteSize":85900,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd"><path fill="#EEE" d="M44.242 59.348c-3.7 1.576-7.3 1.994-10.902.84a7.002 7.002 0 0 1-9.085-.699l-4.243-4.243a7 7 0 0 1-.238-9.649c-.701-3.024-.419-6.083.646-9.206l-6.287-2.426a5.6 5.6 0 0 1-2.274-8.824l8.233-9.811a5.6 5.6 0 0 1 6.306-1.625l8.045 3.105c.772-.797 1.564-1.6 2.374-2.41C44.841 6.376 55.265 2.135 68.09 1.677a10 10 0 0 1 1.119.023c5.507.42 9.63 5.226 9.209 10.733-.935 12.225-5.373 22.309-13.315 30.25a410.76 410.76 0 0 1-1.661 1.653l3.247 8.412a5.6 5.6 0 0 1-1.625 6.306l-9.81 8.233a5.6 5.6 0 0 1-8.825-2.274l-2.186-5.665zm-22.92-26.923l10.406-12.402-6.822-2.633a1.6 1.6 0 0 0-1.801.464l-8.233 9.811a1.6 1.6 0 0 0 .65 2.521l5.8 2.239zm26.646 25.4l2.239 5.8a1.6 1.6 0 0 0 2.521.649l9.81-8.232a1.6 1.6 0 0 0 .465-1.802l-2.633-6.822-12.402 10.406zm-19.69-5.627c8.751 8.752 16.065 5.587 33.995-12.343 7.25-7.25 11.292-16.433 12.155-27.727a6 6 0 0 0-6.196-6.454c-11.846.423-21.303 4.271-28.586 11.554-17.03 17.03-20.414 25.924-11.368 34.97z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M52.54 28.376a4 4 0 1 0 5.656-5.657 4 4 0 0 0-5.657 5.657zm-2.83 2.829A8 8 0 1 1 61.025 19.89a8 8 0 0 1-11.313 11.314z"/><path fill="#FEE1D3" d="M15.063 54.54a2 2 0 0 1 0 2.828L3.749 68.68A2 2 0 1 1 .92 65.853l11.314-11.314a2 2 0 0 1 2.829 0zm9.899 9.899a2 2 0 0 1 0 2.828l-8.485 8.485a2 2 0 1 1-2.829-2.828l8.486-8.485a2 2 0 0 1 2.828 0z"/><path fill="#FDC4A8" d="M20.012 59.489a2 2 0 0 1 0 2.828L4.456 77.874a2 2 0 0 1-2.829-2.829L17.184 59.49a2 2 0 0 1 2.828 0z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(7 3)"><path fill="#EEE" fill-rule="nonzero" d="M54 18a2 2 0 1 1 0-4h4c.843 0 1.675.105 2.48.31a2 2 0 1 1-.99 3.876A6.015 6.015 0 0 0 58 18h-4zm9.735 4.228a2 2 0 0 1 3.822-1.18A10 10 0 0 1 68 24v3.513a2 2 0 1 1-4 0V24c0-.61-.09-1.204-.265-1.772zM64 35.513a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0V66a9.97 9.97 0 0 1-.963 4.286 2 2 0 1 1-3.613-1.716A5.969 5.969 0 0 0 64 66v-2.487zm-5.255 8.441a2 2 0 1 1 .49 3.97c-.401.05-.806.075-1.218.076h-5.042a2 2 0 1 1 0-4h5.038c.246 0 .49-.016.732-.046zM44.975 72a2 2 0 1 1 0 4h-6a2 2 0 1 1 0-4h6zm-14 0a2 2 0 1 1 0 4H26c-.429 0-.855-.027-1.276-.08a2 2 0 0 1 .506-3.969c.254.033.51.049.77.049h4.975zm-10.438-3.514a2 2 0 1 1-3.64 1.66A9.97 9.97 0 0 1 16 66v-2.538a2 2 0 1 1 4 0V66c0 .871.185 1.713.537 2.486zM8 2a6 6 0 0 0-6 6v42a6 6 0 0 0 6 6h32a6 6 0 0 0 6-6V8a6 6 0 0 0-6-6H8zm0-4h32c5.523 0 10 4.477 10 10v42c0 5.523-4.477 10-10 10H8C2.477 60-2 55.523-2 50V8C-2 2.477 2.477-2 8-2z"/><rect width="10" height="4" x="8" y="16" fill="#EFEDF8" rx="2"/><rect width="10" height="4" x="21" y="16" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="8" y="32" fill="#E1DBF1" rx="2"/><rect width="6" height="4" x="34" y="16" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="8" y="24" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="24" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="21" y="32" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="8" y="40" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="40" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="26" y="40" fill="#C3B8E3" rx="2"/><rect width="10" height="4" x="26" y="24" fill="#C3B8E3" rx="2"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(0 3)"><path fill="#EEE" fill-rule="nonzero" d="M40.843 5.864a2 2 0 1 1 .348-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.977-.523zm13.946 1.22a2 2 0 1 1 .349-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.978-.523zm13.947 1.22a2 2 0 1 1 .349-3.984 11.952 11.952 0 0 1 6.655 2.75 2 2 0 1 1-2.569 3.066 7.953 7.953 0 0 0-4.435-1.832zm7.28 7.357a2 2 0 1 1 3.99-.301c.048.639.045 1.283-.01 1.934l-.385 4.4a2 2 0 1 1-3.985-.349l.384-4.395c.037-.433.039-.863.007-1.29zm-1.088 13.654a2 2 0 0 1 3.985.348l-.523 5.978a2 2 0 1 1-3.984-.349l.522-5.977zm-1.22 13.947a2 2 0 1 1 3.985.348l-.523 5.977a2 2 0 1 1-3.985-.348l.523-5.977zM72.305 56.7a2 2 0 0 1 3.79 1.282 11.995 11.995 0 0 1-4.253 5.81 2 2 0 0 1-2.373-3.22 7.996 7.996 0 0 0 2.836-3.872zm-9.054 5.33a2 2 0 1 1-.349 3.985l-5.977-.522a2 2 0 1 1 .349-3.985l5.977.523zM32.793 10.675a2 2 0 1 1-3.675-1.579 12.02 12.02 0 0 1 4.696-5.456 2 2 0 0 1 2.112 3.397 8.02 8.02 0 0 0-3.133 3.638z"/><rect width="48" height="58" x="2" y="14" fill="#FAFAFA" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M12 16a8 8 0 0 0-8 8v38a8 8 0 0 0 8 8h28a8 8 0 0 0 8-8V24a8 8 0 0 0-8-8H12zm0-4h28c6.627 0 12 5.373 12 12v38c0 6.627-5.373 12-12 12H12C5.373 74 0 68.627 0 62V24c0-6.627 5.373-12 12-12z"/><rect width="24" height="4" x="11" y="30" fill="#E5E5E5" rx="2"/><rect width="30" height="4" x="11" y="41" fill="#E5E5E5" rx="2"/><rect width="20" height="4" x="11" y="52" fill="#E5E5E5" rx="2"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="430" height="200" viewBox="0 0 430 200"><g fill="none" fill-rule="evenodd"><g transform="translate(138 65)"><path fill="#E5E5E5" fill-rule="nonzero" d="M35 70a2 2 0 1 1 0-4c2.542 0 5.042-.305 7.463-.904a2 2 0 1 1 .96 3.884A35.075 35.075 0 0 1 35 70zm18.21-5.105a2 2 0 1 1-2.083-3.414 31.143 31.143 0 0 0 5.896-4.664 2 2 0 1 1 2.842 2.815 35.143 35.143 0 0 1-6.654 5.263zM66.106 51.06a2 2 0 0 1-3.552-1.838 30.77 30.77 0 0 0 2.612-7.042 2 2 0 1 1 3.892.922 34.77 34.77 0 0 1-2.952 7.958zm3.816-18.433a2 2 0 1 1-3.991.268 30.873 30.873 0 0 0-1.407-7.38 2 2 0 0 1 3.808-1.223 34.873 34.873 0 0 1 1.59 8.335zm-6.346-17.842a2 2 0 0 1-3.264 2.312 31.188 31.188 0 0 0-5.054-5.564 2 2 0 0 1 2.615-3.027 35.188 35.188 0 0 1 5.703 6.279zM48.895 2.867a2 2 0 0 1-1.59 3.67 30.758 30.758 0 0 0-7.206-2.12 2 2 0 1 1 .653-3.946 34.758 34.758 0 0 1 8.143 2.396zM30.263.318a2 2 0 0 1 .537 3.964c-2.505.339-4.94.98-7.266 1.907a2 2 0 1 1-1.48-3.716A34.774 34.774 0 0 1 30.263.318zM12.907 7.853a2 2 0 0 1 2.527 3.1 31.196 31.196 0 0 0-5.213 5.416 2 2 0 0 1-3.196-2.406 35.196 35.196 0 0 1 5.882-6.11zM1.99 23.343a2 2 0 0 1 3.772 1.331 30.82 30.82 0 0 0-1.619 7.337 2 2 0 1 1-3.982-.38 34.82 34.82 0 0 1 1.829-8.289zM.719 42.086a2 2 0 1 1 3.917-.806 30.757 30.757 0 0 0 2.4 7.118 2 2 0 1 1-3.605 1.73 34.757 34.757 0 0 1-2.713-8.042zM9.393 58.86a2 2 0 0 1 2.926-2.728 31.167 31.167 0 0 0 5.751 4.841 2 2 0 1 1-2.187 3.349 35.167 35.167 0 0 1-6.49-5.462zm16.245 9.873a2 2 0 1 1 1.067-3.855 30.979 30.979 0 0 0 7.434 1.11 2 2 0 1 1-.11 3.998 34.979 34.979 0 0 1-8.391-1.253z"/><circle cx="35" cy="35" r="16" stroke="#E1DBF1" stroke-width="4"/><path fill="#6B4FBB" d="M37 33h5a2 2 0 1 1 0 4h-7a2 2 0 0 1-2-2v-8a2 2 0 1 1 4 0v6z"/></g><g transform="translate(247 30)"><rect width="116" height="135" y="5" fill="#F9F9F9" rx="10"/><rect width="116" height="134" x="5" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="10"/><g transform="translate(23 23)"><rect width="16" height="4" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="32" y="12" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="44" fill="#EEE" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="32" y="36" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="48" fill="#E1DBF1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#FC6D26" rx="2"/><rect width="4" height="4" x="56" y="36" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="64" y="60" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="72" y="60" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="28" height="4" y="36" fill="#EEE" rx="2"/><rect width="28" height="4" x="44" y="48" fill="#EEE" rx="2"/><rect width="28" height="4" x="32" y="60" fill="#EFEDF8" rx="2"/><rect width="28" height="4" y="12" fill="#6B4FBB" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#C3B8E3" rx="2"/><rect width="8" height="4" y="24" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6B4FBB" rx="2"/><rect width="12" height="4" y="48" fill="#FC6D26" rx="2"/><rect width="12" height="4" y="60" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="60" fill="#FEF0E8" rx="2"/></g><g transform="translate(23 95)"><rect width="16" height="4" fill="#EFEDF8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#FC6D26" rx="2"/><rect width="16" height="4" x="44" fill="#6B4FBB" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="38" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="14" height="4" y="12" fill="#EEE" rx="2"/></g></g><path fill="#FC6D26" fill-rule="nonzero" d="M81 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15zm-5-20a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2zm10 0a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2z"/><path fill="#E5E5E5" fill-rule="nonzero" d="M108 102c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm93 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2z"/></g></svg>
\ No newline at end of file
...@@ -178,7 +178,7 @@ const Api = { ...@@ -178,7 +178,7 @@ const Api = {
issueTemplate(namespacePath, projectPath, key, type, callback) { issueTemplate(namespacePath, projectPath, key, type, callback) {
const url = Api.buildUrl(Api.issuableTemplatePath) const url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', key) .replace(':key', encodeURIComponent(key))
.replace(':type', type) .replace(':type', type)
.replace(':project_path', projectPath) .replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath); .replace(':namespace_path', namespacePath);
......
...@@ -299,6 +299,13 @@ const gfmRules = { ...@@ -299,6 +299,13 @@ const gfmRules = {
export class CopyAsGFM { export class CopyAsGFM {
constructor() { constructor() {
// iOS currently does not support clipboardData.setData(). This bug should
// be fixed in iOS 12, but for now we'll disable this for all iOS browsers
// ref: https://trac.webkit.org/changeset/222228/webkit
const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
if (isIOS) return;
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM); $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
......
...@@ -2,18 +2,19 @@ import { n__ } from '../locale'; ...@@ -2,18 +2,19 @@ import { n__ } from '../locale';
import { convertPermissionToBoolean } from '../lib/utils/common_utils'; import { convertPermissionToBoolean } from '../lib/utils/common_utils';
export default class SecretValues { export default class SecretValues {
constructor(container) { constructor({
container,
valueSelector = '.js-secret-value',
placeholderSelector = '.js-secret-value-placeholder',
}) {
this.container = container; this.container = container;
this.valueSelector = valueSelector;
this.placeholderSelector = placeholderSelector;
} }
init() { init() {
this.values = this.container.querySelectorAll('.js-secret-value');
this.placeholders = this.container.querySelectorAll('.js-secret-value-placeholder');
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button'); this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
this.revealText = n__('Reveal value', 'Reveal values', this.values.length);
this.hideText = n__('Hide value', 'Hide values', this.values.length);
const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus); const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
this.updateDom(isRevealed); this.updateDom(isRevealed);
...@@ -28,15 +29,17 @@ export default class SecretValues { ...@@ -28,15 +29,17 @@ export default class SecretValues {
} }
updateDom(isRevealed) { updateDom(isRevealed) {
this.values.forEach((value) => { const values = this.container.querySelectorAll(this.valueSelector);
values.forEach((value) => {
value.classList.toggle('hide', !isRevealed); value.classList.toggle('hide', !isRevealed);
}); });
this.placeholders.forEach((placeholder) => { const placeholders = this.container.querySelectorAll(this.placeholderSelector);
placeholders.forEach((placeholder) => {
placeholder.classList.toggle('hide', isRevealed); placeholder.classList.toggle('hide', isRevealed);
}); });
this.revealButton.textContent = isRevealed ? this.hideText : this.revealText; this.revealButton.textContent = isRevealed ? n__('Hide value', 'Hide values', values.length) : n__('Reveal value', 'Reveal values', values.length);
this.revealButton.dataset.secretRevealStatus = isRevealed; this.revealButton.dataset.secretRevealStatus = isRevealed;
} }
} }
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
import ClustersService from './services/clusters_service'; import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store'; import ClustersStore from './stores/clusters_store';
import applications from './components/applications.vue'; import applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons';
/** /**
* Cluster page has 2 separate parts: * Cluster page has 2 separate parts:
...@@ -48,12 +49,9 @@ export default class Clusters { ...@@ -48,12 +49,9 @@ export default class Clusters {
installPrometheusEndpoint: installPrometheusPath, installPrometheusEndpoint: installPrometheusPath,
}); });
this.toggle = this.toggle.bind(this);
this.installApplication = this.installApplication.bind(this); this.installApplication = this.installApplication.bind(this);
this.showToken = this.showToken.bind(this); this.showToken = this.showToken.bind(this);
this.toggleButton = document.querySelector('.js-toggle-cluster');
this.toggleInput = document.querySelector('.js-toggle-input');
this.errorContainer = document.querySelector('.js-cluster-error'); this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success'); this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating'); this.creatingContainer = document.querySelector('.js-cluster-creating');
...@@ -63,6 +61,7 @@ export default class Clusters { ...@@ -63,6 +61,7 @@ export default class Clusters {
this.tokenField = document.querySelector('.js-cluster-token'); this.tokenField = document.querySelector('.js-cluster-token');
initSettingsPanels(); initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications(); this.initApplications();
if (this.store.state.status !== 'created') { if (this.store.state.status !== 'created') {
...@@ -101,13 +100,11 @@ export default class Clusters { ...@@ -101,13 +100,11 @@ export default class Clusters {
} }
addListeners() { addListeners() {
this.toggleButton.addEventListener('click', this.toggle);
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication); eventHub.$on('installApplication', this.installApplication);
} }
removeListeners() { removeListeners() {
this.toggleButton.removeEventListener('click', this.toggle);
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken); if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication); eventHub.$off('installApplication', this.installApplication);
} }
...@@ -151,11 +148,6 @@ export default class Clusters { ...@@ -151,11 +148,6 @@ export default class Clusters {
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason); this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
} }
toggle() {
this.toggleButton.classList.toggle('is-checked');
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('is-checked').toString());
}
showToken() { showToken() {
const type = this.tokenField.getAttribute('type'); const type = this.tokenField.getAttribute('type');
......
import Flash from '../flash'; import Flash from '../flash';
import { s__ } from '../locale'; import { s__ } from '../locale';
import setupToggleButtons from '../toggle_buttons';
import ClustersService from './services/clusters_service'; import ClustersService from './services/clusters_service';
/**
* Toggles loading and disabled classes.
* @param {HTMLElement} button
*/
const toggleLoadingButton = (button) => {
if (button.getAttribute('disabled')) {
button.removeAttribute('disabled');
} else {
button.setAttribute('disabled', true);
}
button.classList.toggle('is-loading');
};
/** export default () => {
* Toggles checked class for the given button const clusterList = document.querySelector('.js-clusters-list');
* @param {HTMLElement} button // The empty state won't have a clusterList
*/ if (clusterList) {
const toggleValue = (button) => { setupToggleButtons(
button.classList.toggle('is-checked'); document.querySelector('.js-clusters-list'),
(value, toggle) =>
ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } })
.catch((err) => {
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
throw err;
}),
);
}
}; };
/**
* Handles toggle buttons in the cluster's table.
*
* When the user clicks the toggle button for each cluster, it:
* - toggles the button
* - shows a loading and disables button
* - Makes a put request to the given endpoint
* Once we receive the response, either:
* 1) Show updated status in case of successfull response
* 2) Show initial status in case of failed response
*/
export default function setClusterTableToggles() {
document.querySelectorAll('.js-toggle-cluster-list')
.forEach(button => button.addEventListener('click', (e) => {
const toggleButton = e.currentTarget;
const endpoint = toggleButton.getAttribute('data-endpoint');
toggleValue(toggleButton);
toggleLoadingButton(toggleButton);
const value = toggleButton.classList.contains('is-checked');
ClustersService.updateCluster(endpoint, { cluster: { enabled: value } })
.then(() => {
toggleLoadingButton(toggleButton);
})
.catch(() => {
toggleLoadingButton(toggleButton);
toggleValue(toggleButton);
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
});
}));
}
...@@ -87,6 +87,7 @@ ...@@ -87,6 +87,7 @@
<div v-else-if="hasKeys"> <div v-else-if="hasKeys">
<keys-panel <keys-panel
title="Enabled deploy keys for this project" title="Enabled deploy keys for this project"
class="qa-project-deploy-keys"
:keys="keys.enabled_keys" :keys="keys.enabled_keys"
:store="store" :store="store"
:endpoint="endpoint" :endpoint="endpoint"
......
...@@ -53,10 +53,10 @@ ...@@ -53,10 +53,10 @@
</i> </i>
</div> </div>
<div class="deploy-key-content key-list-item-info"> <div class="deploy-key-content key-list-item-info">
<strong class="title"> <strong class="title qa-key-title">
{{ deployKey.title }} {{ deployKey.title }}
</strong> </strong>
<div class="description"> <div class="description qa-key-fingerprint">
{{ deployKey.fingerprint }} {{ deployKey.fingerprint }}
</div> </div>
</div> </div>
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
import Milestone from './milestone';
import NotificationsForm from './notifications_form';
import notificationsDropdown from './notifications_dropdown';
import LineHighlighter from './line_highlighter';
import MergeRequest from './merge_request'; import MergeRequest from './merge_request';
import Sidebar from './right_sidebar';
import Flash from './flash'; import Flash from './flash';
import SecretValues from './behaviors/secret_values';
import UserCallout from './user_callout';
import BlobViewer from './blob/viewer/index';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
import Star from './star';
import TreeView from './tree';
import ZenMode from './zen_mode'; import ZenMode from './zen_mode';
import initSettingsPanels from './settings_panels';
import PerformanceBar from './performance_bar';
import initNotes from './init_notes'; import initNotes from './init_notes';
import initIssuableSidebar from './init_issuable_sidebar'; import initIssuableSidebar from './init_issuable_sidebar';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import { convertPermissionToBoolean } from './lib/utils/common_utils';
import GlFieldErrors from './gl_field_errors'; import GlFieldErrors from './gl_field_errors';
import GLForm from './gl_form';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
import ShortcutsNavigation from './shortcuts_navigation';
import ShortcutsIssuable from './shortcuts_issuable'; import ShortcutsIssuable from './shortcuts_issuable';
import Diff from './diff'; import Diff from './diff';
import SearchAutocomplete from './search_autocomplete'; import SearchAutocomplete from './search_autocomplete';
import Activities from './activities';
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -76,6 +61,11 @@ import Activities from './activities'; ...@@ -76,6 +61,11 @@ import Activities from './activities';
.catch(fail); .catch(fail);
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'projects:environments:metrics':
import('./pages/projects/environments/metrics')
.then(callDefault)
.catch(fail);
break;
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
import('./pages/projects/merge_requests/index') import('./pages/projects/merge_requests/index')
.then(callDefault) .then(callDefault)
...@@ -99,10 +89,20 @@ import Activities from './activities'; ...@@ -99,10 +89,20 @@ import Activities from './activities';
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'projects:milestones:index':
import('./pages/projects/milestones/index')
.then(callDefault)
.catch(fail);
break;
case 'projects:milestones:show': case 'projects:milestones:show':
import('./pages/projects/milestones/show')
.then(callDefault)
.catch(fail);
break;
case 'groups:milestones:show': case 'groups:milestones:show':
new Milestone(); import('./pages/groups/milestones/show')
new Sidebar(); .then(callDefault)
.catch(fail);
break; break;
case 'dashboard:milestones:show': case 'dashboard:milestones:show':
import('./pages/dashboard/milestones/show') import('./pages/dashboard/milestones/show')
...@@ -130,7 +130,9 @@ import Activities from './activities'; ...@@ -130,7 +130,9 @@ import Activities from './activities';
.catch(fail); .catch(fail);
break; break;
case 'dashboard:todos:index': case 'dashboard:todos:index':
import('./pages/dashboard/todos/index').then(callDefault).catch(fail); import('./pages/dashboard/todos/index')
.then(callDefault)
.catch(fail);
break; break;
case 'admin:jobs:index': case 'admin:jobs:index':
import('./pages/admin/jobs/index') import('./pages/admin/jobs/index')
...@@ -233,15 +235,21 @@ import Activities from './activities'; ...@@ -233,15 +235,21 @@ import Activities from './activities';
.catch(fail); .catch(fail);
break; break;
case 'projects:snippets:show': case 'projects:snippets:show':
initNotes(); import('./pages/projects/snippets/show')
new ZenMode(); .then(callDefault)
.catch(fail);
break; break;
case 'projects:snippets:new': case 'projects:snippets:new':
case 'projects:snippets:edit':
case 'projects:snippets:create': case 'projects:snippets:create':
import('./pages/projects/snippets/new')
.then(callDefault)
.catch(fail);
break;
case 'projects:snippets:edit':
case 'projects:snippets:update': case 'projects:snippets:update':
new GLForm($('.snippet-form'), true); import('./pages/projects/snippets/edit')
new ZenMode(); .then(callDefault)
.catch(fail);
break; break;
case 'snippets:new': case 'snippets:new':
import('./pages/snippets/new') import('./pages/snippets/new')
...@@ -264,8 +272,9 @@ import Activities from './activities'; ...@@ -264,8 +272,9 @@ import Activities from './activities';
.catch(fail); .catch(fail);
break; break;
case 'projects:releases:edit': case 'projects:releases:edit':
new ZenMode(); import('./pages/projects/releases/edit')
new GLForm($('.release-form'), true); .then(callDefault)
.catch(fail);
break; break;
case 'projects:merge_requests:show': case 'projects:merge_requests:show':
new Diff(); new Diff();
...@@ -309,19 +318,10 @@ import Activities from './activities'; ...@@ -309,19 +318,10 @@ import Activities from './activities';
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'projects:show': case 'projects:show':
shortcut_handler = new ShortcutsNavigation(); import('./pages/projects/show')
new NotificationsForm(); .then(callDefault)
new UserCallout({ .catch(fail);
setCalloutPerProject: true, shortcut_handler = true;
className: 'js-autodevops-banner',
});
if ($('#tree-slider').length) new TreeView();
if ($('.blob-viewer').length) new BlobViewer();
if ($('.project-show-activity').length) new Activities();
$('#tree-slider').waitForImages(function() {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
break; break;
case 'projects:edit': case 'projects:edit':
import('./pages/projects/edit') import('./pages/projects/edit')
...@@ -476,18 +476,15 @@ import Activities from './activities'; ...@@ -476,18 +476,15 @@ import Activities from './activities';
.catch(fail); .catch(fail);
break; break;
case 'projects:settings:repository:show': case 'projects:settings:repository:show':
// Initialize expandable settings panels import('./pages/projects/settings/repository/show')
initSettingsPanels(); .then(callDefault)
.catch(fail);
break; break;
case 'projects:settings:ci_cd:show': case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels import('./pages/projects/settings/ci_cd/show')
initSettingsPanels(); .then(callDefault)
.catch(fail);
const runnerToken = document.querySelector('.js-secret-runner-token'); break;
if (runnerToken) {
const runnerTokenSecretValue = new SecretValues(runnerToken);
runnerTokenSecretValue.init();
}
case 'groups:settings:ci_cd:show': case 'groups:settings:ci_cd:show':
import('./pages/groups/settings/ci_cd/show') import('./pages/groups/settings/ci_cd/show')
.then(callDefault) .then(callDefault)
...@@ -495,13 +492,19 @@ import Activities from './activities'; ...@@ -495,13 +492,19 @@ import Activities from './activities';
break; break;
case 'ci:lints:create': case 'ci:lints:create':
case 'ci:lints:show': case 'ci:lints:show':
import('./pages/ci/lints').then(m => m.default()).catch(fail); import('./pages/ci/lints')
.then(callDefault)
.catch(fail);
break; break;
case 'users:show': case 'users:show':
import('./pages/users/show').then(callDefault).catch(fail); import('./pages/users/show')
.then(callDefault)
.catch(fail);
break; break;
case 'admin:conversational_development_index:show': case 'admin:conversational_development_index:show':
import('./pages/admin/conversational_development_index/show').then(m => m.default()).catch(fail); import('./pages/admin/conversational_development_index/show')
.then(callDefault)
.catch(fail);
break; break;
case 'snippets:show': case 'snippets:show':
import('./pages/snippets/show') import('./pages/snippets/show')
...@@ -509,7 +512,9 @@ import Activities from './activities'; ...@@ -509,7 +512,9 @@ import Activities from './activities';
.catch(fail); .catch(fail);
break; break;
case 'import:fogbugz:new_user_map': case 'import:fogbugz:new_user_map':
import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail); import('./pages/import/fogbugz/new_user_map')
.then(callDefault)
.catch(fail);
break; break;
case 'profiles:personal_access_tokens:index': case 'profiles:personal_access_tokens:index':
import('./pages/profiles/personal_access_tokens') import('./pages/profiles/personal_access_tokens')
...@@ -533,6 +538,11 @@ import Activities from './activities'; ...@@ -533,6 +538,11 @@ import Activities from './activities';
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'dashboard:groups:index':
import('./pages/dashboard/groups/index')
.then(callDefault)
.catch(fail);
break;
} }
switch (path[0]) { switch (path[0]) {
case 'sessions': case 'sessions':
...@@ -616,23 +626,12 @@ import Activities from './activities'; ...@@ -616,23 +626,12 @@ import Activities from './activities';
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'show':
new Star();
notificationsDropdown();
break;
case 'wikis': case 'wikis':
import('./pages/projects/wikis') import('./pages/projects/wikis')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'snippets':
if (path[2] === 'show') {
new ZenMode();
new LineHighlighter();
new BlobViewer();
}
break;
} }
break; break;
} }
...@@ -642,7 +641,9 @@ import Activities from './activities'; ...@@ -642,7 +641,9 @@ import Activities from './activities';
} }
if (document.querySelector('#peek')) { if (document.querySelector('#peek')) {
new PerformanceBar({ container: '#peek' }); import('./performance_bar')
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
.catch(fail);
} }
}; };
......
...@@ -99,7 +99,7 @@ export default { ...@@ -99,7 +99,7 @@ export default {
> >
<div <div
v-if="model.isLoadingFolderContent" v-if="model.isLoadingFolderContent"
:key="i"> :key="`loading-item-${i}`">
<loading-icon size="2" /> <loading-icon size="2" />
</div> </div>
...@@ -110,10 +110,10 @@ export default { ...@@ -110,10 +110,10 @@ export default {
:model="children" :model="children"
:can-create-deployment="canCreateDeployment" :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment" :can-read-environment="canReadEnvironment"
:key="index" :key="`env-item-${i}-${index}`"
/> />
<div :key="i"> <div :key="`sub-div-${i}`">
<div class="text-center prepend-top-10"> <div class="text-center prepend-top-10">
<a <a
:href="folderUrl(model)" :href="folderUrl(model)"
......
...@@ -118,14 +118,14 @@ export const showSubLevelItems = (el) => { ...@@ -118,14 +118,14 @@ export const showSubLevelItems = (el) => {
moveSubItemsToPosition(el, subItems); moveSubItemsToPosition(el, subItems);
}; };
export const mouseEnterTopItems = (el) => { export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
if (currentOpenMenu) hideMenu(currentOpenMenu); if (currentOpenMenu) hideMenu(currentOpenMenu);
showSubLevelItems(el); showSubLevelItems(el);
}, getHideSubItemsInterval()); }, timeout);
}; };
export const mouseLeaveTopItem = (el) => { export const mouseLeaveTopItem = (el) => {
......
...@@ -10,7 +10,7 @@ import groupItemComponent from './components/group_item.vue'; ...@@ -10,7 +10,7 @@ import groupItemComponent from './components/group_item.vue';
Vue.use(Translate); Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => { export default () => {
const el = document.getElementById('js-groups-tree'); const el = document.getElementById('js-groups-tree');
// Don't do anything if element doesn't exist (No groups) // Don't do anything if element doesn't exist (No groups)
...@@ -71,4 +71,4 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -71,4 +71,4 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
}, },
}); });
}); };
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import '../../vue_shared/vue_resource_interceptor';
Vue.use(VueResource);
export default class GroupsService { export default class GroupsService {
constructor(endpoint) { constructor(endpoint) {
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
> >
<repo-tab <repo-tab
v-for="tab in openFiles" v-for="tab in openFiles"
:key="tab.id" :key="tab.key"
:tab="tab" :tab="tab"
/> />
</ul> </ul>
......
...@@ -22,3 +22,11 @@ axios.interceptors.response.use((config) => { ...@@ -22,3 +22,11 @@ axios.interceptors.response.use((config) => {
}); });
export default axios; export default axios;
/**
* @return The adapter that axios uses for dispatching requests. This may be overwritten in tests.
*
* @see https://github.com/axios/axios/tree/master/lib/adapters
* @see https://github.com/ctimmerm/axios-mock-adapter/blob/v1.12.0/src/index.js#L39
*/
export const getDefaultAdapter = () => axios.defaults.adapter;
import Vue from 'vue'; import Vue from 'vue';
import Dashboard from './components/dashboard.vue'; import Dashboard from './components/dashboard.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({ export default () => new Vue({
el: '#prometheus-graphs', el: '#prometheus-graphs',
render: createElement => createElement(Dashboard), render: createElement => createElement(Dashboard),
})); });
import initGroupsList from '../../../../groups';
export default () => {
initGroupsList();
};
import GroupsList from '~/groups_list'; import GroupsList from '~/groups_list';
import Landing from '~/landing'; import Landing from '~/landing';
import initGroupsList from '../../../groups';
export default function () { export default function () {
new GroupsList(); // eslint-disable-line no-new new GroupsList(); // eslint-disable-line no-new
initGroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing'); const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) return; if (!landingElement) return;
const exploreGroupsLanding = new Landing( const exploreGroupsLanding = new Landing(
......
import Activities from '~/activities'; import Activities from '~/activities';
export default new Activities(); export default () => new Activities();
import Labels from '~/labels'; import Labels from '~/labels';
export default new Labels(); export default () => new Labels();
import Labels from '~/labels'; import Labels from '~/labels';
export default new Labels(); export default () => new Labels();
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
export default initMilestonesShow;
...@@ -3,7 +3,9 @@ import SecretValues from '~/behaviors/secret_values'; ...@@ -3,7 +3,9 @@ import SecretValues from '~/behaviors/secret_values';
export default () => { export default () => {
const secretVariableTable = document.querySelector('.js-secret-variable-table'); const secretVariableTable = document.querySelector('.js-secret-variable-table');
if (secretVariableTable) { if (secretVariableTable) {
const secretVariableTableValues = new SecretValues(secretVariableTable); const secretVariableTableValues = new SecretValues({
container: secretVariableTable,
});
secretVariableTableValues.init(); secretVariableTableValues.init();
} }
}; };
...@@ -5,6 +5,7 @@ import notificationsDropdown from '~/notifications_dropdown'; ...@@ -5,6 +5,7 @@ import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form'; import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list'; import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/shortcuts_navigation'; import ShortcutsNavigation from '~/shortcuts_navigation';
import initGroupsList from '../../../groups';
export default () => { export default () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup'); const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
...@@ -16,4 +17,6 @@ export default () => { ...@@ -16,4 +17,6 @@ export default () => {
if (newGroupChildWrapper) { if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper); new NewGroupChild(newGroupChildWrapper);
} }
initGroupsList();
}; };
<script>
import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import modal from '~/vue_shared/components/modal.vue';
import { n__, s__, sprintf } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
export default {
components: {
modal,
},
props: {
issueCount: {
type: Number,
required: true,
},
mergeRequestCount: {
type: Number,
required: true,
},
milestoneId: {
type: Number,
required: true,
},
milestoneTitle: {
type: String,
required: true,
},
milestoneUrl: {
type: String,
required: true,
},
},
computed: {
text() {
const milestoneTitle = sprintf('<strong>%{milestoneTitle}</strong>', { milestoneTitle: this.milestoneTitle });
if (this.issueCount === 0 && this.mergeRequestCount === 0) {
return sprintf(
s__(`Milestones|
You’re about to permanently delete the milestone %{milestoneTitle} from this project.
%{milestoneTitle} is not currently used in any issues or merge requests.`),
{
milestoneTitle,
},
false,
);
}
return sprintf(
s__(`Milestones|
You’re about to permanently delete the milestone %{milestoneTitle} from this project and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}.
Once deleted, it cannot be undone or recovered.`),
{
milestoneTitle,
issuesWithCount: n__('%d issue', '%d issues', this.issueCount),
mergeRequestsWithCount: n__('%d merge request', '%d merge requests', this.mergeRequestCount),
},
false,
);
},
title() {
return sprintf(s__('Milestones|Delete milestone %{milestoneTitle}?'), { milestoneTitle: this.milestoneTitle });
},
},
methods: {
onSubmit() {
eventHub.$emit('deleteMilestoneModal.requestStarted', this.milestoneUrl);
return axios.delete(this.milestoneUrl)
.then((response) => {
eventHub.$emit('deleteMilestoneModal.requestFinished', { milestoneUrl: this.milestoneUrl, successful: true });
// follow the rediect to milestones overview page
redirectTo(response.request.responseURL);
})
.catch((error) => {
eventHub.$emit('deleteMilestoneModal.requestFinished', { milestoneUrl: this.milestoneUrl, successful: false });
if (error.response && error.response.status === 404) {
Flash(sprintf(s__('Milestones|Milestone %{milestoneTitle} was not found'), { milestoneTitle: this.milestoneTitle }));
} else {
Flash(sprintf(s__('Milestones|Failed to delete milestone %{milestoneTitle}'), { milestoneTitle: this.milestoneTitle }));
}
throw error;
});
},
},
};
</script>
<template>
<modal
id="delete-milestone-modal"
:title="title"
:text="text"
kind="danger"
:primary-button-label="s__('Milestones|Delete milestone')"
@submit="onSubmit">
<template
slot="body"
slot-scope="props">
<p v-html="props.text"></p>
</template>
</modal>
</template>
import Vue from 'vue';
export default new Vue();
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import deleteMilestoneModal from './components/delete_milestone_modal.vue';
import eventHub from './event_hub';
export default () => {
Vue.use(Translate);
const onRequestFinished = ({ milestoneUrl, successful }) => {
const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
if (!successful) {
button.removeAttribute('disabled');
}
button.querySelector('.js-loading-icon').classList.add('hidden');
};
const onRequestStarted = (milestoneUrl) => {
const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
button.setAttribute('disabled', '');
button.querySelector('.js-loading-icon').classList.remove('hidden');
eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
};
const onDeleteButtonClick = (event) => {
const button = event.currentTarget;
const modalProps = {
milestoneId: parseInt(button.dataset.milestoneId, 10),
milestoneTitle: button.dataset.milestoneTitle,
milestoneUrl: button.dataset.milestoneUrl,
issueCount: parseInt(button.dataset.milestoneIssueCount, 10),
mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10),
};
eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted);
eventHub.$emit('deleteMilestoneModal.props', modalProps);
};
const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
for (let i = 0; i < deleteMilestoneButtons.length; i += 1) {
const button = deleteMilestoneButtons[i];
button.addEventListener('click', onDeleteButtonClick);
}
eventHub.$once('deleteMilestoneModal.mounted', () => {
for (let i = 0; i < deleteMilestoneButtons.length; i += 1) {
const button = deleteMilestoneButtons[i];
button.removeAttribute('disabled');
}
});
return new Vue({
el: '#delete-milestone-modal',
components: {
deleteMilestoneModal,
},
data() {
return {
modalProps: {
milestoneId: -1,
milestoneTitle: '',
milestoneUrl: '',
issueCount: -1,
mergeRequestCount: -1,
},
};
},
mounted() {
eventHub.$on('deleteMilestoneModal.props', this.setModalProps);
eventHub.$emit('deleteMilestoneModal.mounted');
},
beforeDestroy() {
eventHub.$off('deleteMilestoneModal.props', this.setModalProps);
},
methods: {
setModalProps(modalProps) {
this.modalProps = modalProps;
},
},
render(createElement) {
return createElement(deleteMilestoneModal, {
props: this.modalProps,
});
},
});
};
/* eslint-disable no-new */
import Milestone from '~/milestone';
import Sidebar from '~/right_sidebar';
export default () => {
new Milestone();
new Sidebar();
};
import monitoringBundle from '~/monitoring/monitoring_bundle';
export default monitoringBundle;
import ZenMode from '~/zen_mode';
import GLForm from '~/gl_form';
export default function ($formEl) {
new ZenMode(); // eslint-disable-line no-new
new GLForm($formEl, true); // eslint-disable-line no-new
}
import milestones from '~/pages/milestones/shared';
export default milestones;
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
import milestones from '~/pages/milestones/shared';
export default () => {
initMilestonesShow();
milestones();
};
import initForm from '~/pages/projects/init_form';
export default initForm($('.release-form'));
import initSettingsPanels from '~/settings_panels';
import SecretValues from '~/behaviors/secret_values';
export default function () {
// Initialize expandable settings panels
initSettingsPanels();
const runnerToken = document.querySelector('.js-secret-runner-token');
if (runnerToken) {
const runnerTokenSecretValue = new SecretValues({
container: runnerToken,
});
runnerTokenSecretValue.init();
}
const secretVariableTable = document.querySelector('.js-secret-variable-table');
if (secretVariableTable) {
const secretVariableTableValues = new SecretValues({
container: secretVariableTable,
});
secretVariableTableValues.init();
}
}
import initSettingsPanels from '~/settings_panels';
export default initSettingsPanels;
...@@ -215,7 +215,7 @@ ...@@ -215,7 +215,7 @@
</div> </div>
<span class="help-block">{{ visibilityLevelDescription }}</span> <span class="help-block">{{ visibilityLevelDescription }}</span>
<label <label
v-if="visibilityLevel !== visibilityOptions.PUBLIC" v-if="visibilityLevel !== visibilityOptions.PRIVATE"
class="request-access" class="request-access"
> >
<input <input
......
import ShortcutsNavigation from '~/shortcuts_navigation';
import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout';
import TreeView from '~/tree';
import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
export default () => {
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
new NotificationsForm(); // eslint-disable-line no-new
new UserCallout({ // eslint-disable-line no-new
setCalloutPerProject: true,
className: 'js-autodevops-banner',
});
if ($('#tree-slider').length) new TreeView(); // eslint-disable-line no-new
if ($('.blob-viewer').length) new BlobViewer(); // eslint-disable-line no-new
if ($('.project-show-activity').length) new Activities(); // eslint-disable-line no-new
$('#tree-slider').waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
};
import initForm from '~/pages/projects/init_form';
export default initForm($('.snippet-form'));
import initForm from '~/pages/projects/init_form';
export default initForm($('.snippet-form'));
import initNotes from '~/init_notes';
import ZenMode from '~/zen_mode';
import LineHighlighter from '../../../../line_highlighter';
import BlobViewer from '../../../../blob/viewer';
export default function () {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
}
...@@ -18,7 +18,7 @@ function renderWithKaTeX(elements) { ...@@ -18,7 +18,7 @@ function renderWithKaTeX(elements) {
const display = $this.attr('data-math-style') === 'display'; const display = $this.attr('data-math-style') === 'display';
try { try {
katex.render($this.text(), mathNode.get(0), { displayMode: display }); katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
mathNode.insertAfter($this); mathNode.insertAfter($this);
$this.remove(); $this.remove();
} catch (err) { } catch (err) {
......
...@@ -32,8 +32,8 @@ export default class IssuableTemplateSelector extends TemplateSelector { ...@@ -32,8 +32,8 @@ export default class IssuableTemplateSelector extends TemplateSelector {
this.startLoadingSpinner(); this.startLoadingSpinner();
Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => { Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => {
this.currentTemplate = currentTemplate; this.currentTemplate = currentTemplate;
if (err) return; // Error handled by global AJAX error handler
this.stopLoadingSpinner(); this.stopLoadingSpinner();
if (err) return; // Error handled by global AJAX error handler
this.setInputValueToTemplateContent(); this.setInputValueToTemplateContent();
}); });
return; return;
......
import $ from 'jquery';
import Flash from './flash';
import { __ } from './locale';
import { convertPermissionToBoolean } from './lib/utils/common_utils';
/*
example HAML:
```
%button.js-project-feature-toggle.project-feature-toggle{ type: "button",
class: "#{'is-checked' if enabled?}",
'aria-label': _('Toggle Cluster') }
%input{ type: "hidden", class: 'js-project-feature-toggle-input', value: enabled? }
```
*/
function updatetoggle(toggle, isOn) {
toggle.classList.toggle('is-checked', isOn);
}
function onToggleClicked(toggle, input, clickCallback) {
const previousIsOn = convertPermissionToBoolean(input.value);
// Visually change the toggle and start loading
updatetoggle(toggle, !previousIsOn);
toggle.setAttribute('disabled', true);
toggle.classList.toggle('is-loading', true);
Promise.resolve(clickCallback(!previousIsOn, toggle))
.then(() => {
// Actually change the input value
input.setAttribute('value', !previousIsOn);
})
.catch(() => {
// Revert the visuals if something goes wrong
updatetoggle(toggle, previousIsOn);
})
.then(() => {
// Remove the loading indicator in any case
toggle.removeAttribute('disabled');
toggle.classList.toggle('is-loading', false);
$(input).trigger('trigger-change');
})
.catch(() => {
Flash(__('Something went wrong when toggling the button'));
});
}
export default function setupToggleButtons(container, clickCallback = () => {}) {
const toggles = container.querySelectorAll('.js-project-feature-toggle');
toggles.forEach((toggle) => {
const input = toggle.querySelector('.js-project-feature-toggle-input');
const isOn = convertPermissionToBoolean(input.value);
// Get the visible toggle in sync with the hidden input
updatetoggle(toggle, isOn);
toggle.addEventListener('click', onToggleClicked.bind(null, toggle, input, clickCallback));
});
}
...@@ -2,7 +2,7 @@ import { getTimeago } from '~/lib/utils/datetime_utility'; ...@@ -2,7 +2,7 @@ import { getTimeago } from '~/lib/utils/datetime_utility';
import { visitUrl } from '../../lib/utils/url_utility'; import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash'; import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage'; import MemoryUsage from './mr_widget_memory_usage';
import StatusIcon from './mr_widget_status_icon'; import StatusIcon from './mr_widget_status_icon.vue';
import MRWidgetService from '../services/mr_widget_service'; import MRWidgetService from '../services/mr_widget_service';
export default { export default {
......
import ciIcon from '../../vue_shared/components/ci_icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
props: {
status: { type: String, required: true },
showDisabledButton: { type: Boolean, required: false },
},
components: {
ciIcon,
loadingIcon,
},
computed: {
statusObj() {
return {
group: this.status,
icon: `status_${this.status}`,
};
},
},
template: `
<div class="space-children flex-container-block append-right-10">
<div v-if="status === 'loading'" class="mr-widget-icon">
<loading-icon />
</div>
<ci-icon v-else :status="statusObj" />
<button
v-if="showDisabledButton"
type="button"
class="js-disabled-merge-button btn btn-success btn-sm"
disabled="true">
Merge
</button>
</div>
`,
};
<script>
import ciIcon from '../../vue_shared/components/ci_icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
ciIcon,
loadingIcon,
},
props: {
status: {
type: String,
required: true,
},
showDisabledButton: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isLoading() {
return this.status === 'loading';
},
statusObj() {
return {
group: this.status,
icon: `status_${this.status}`,
};
},
},
};
</script>
<template>
<div class="space-children flex-container-block append-right-10">
<div
v-if="isLoading"
class="mr-widget-icon"
>
<loading-icon />
</div>
<ci-icon
v-else
:status="statusObj"
/>
<button
v-if="showDisabledButton"
type="button"
class="js-disabled-merge-button btn btn-success btn-sm"
disabled="true"
>
{{ s__("mrWidget|Merge") }}
</button>
</div>
</template>
<script> <script>
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetArchived', name: 'MRWidgetArchived',
......
<script> <script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetAutoMergeFailed', name: 'MRWidgetAutoMergeFailed',
......
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetChecking',
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="loading" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Checking ability to merge automatically
</span>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetChecking',
components: {
statusIcon,
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="loading"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
{{ s__("mrWidget|Checking ability to merge automatically") }}
</span>
</div>
</div>
</template>
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetClosed',
props: {
mr: { type: Object, required: true },
},
components: {
'mr-widget-author-and-time': mrWidgetAuthorTime,
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" />
<div class="media-body">
<mr-widget-author-and-time
actionText="Closed by"
:author="mr.metrics.closedBy"
:dateTitle="mr.metrics.closedAt"
:dateReadable="mr.metrics.readableClosedAt"
/>
<section class="mr-info-list">
<p>
The changes were not merged into
<a
:href="mr.targetBranchPath"
class="label-branch">
{{mr.targetBranch}}</a>
</p>
</section>
</div>
</div>
`,
};
<script>
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetClosed',
components: {
mrWidgetAuthorTime,
statusIcon,
},
props: {
/* TODO: This is providing all store and service down when it
only needs metrics and targetBranch */
mr: {
type: Object,
required: true,
default: () => ({}),
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
/>
<div class="media-body">
<mr-widget-author-time
:action-text="s__('mrWidget|Closed by')"
:author="mr.metrics.closedBy"
:date-title="mr.metrics.closedAt"
:date-readable="mr.metrics.readableClosedAt"
/>
<section class="mr-info-list">
<p>
{{ s__("mrWidget|The changes were not merged into") }}
<a
:href="mr.targetBranchPath"
class="label-branch"
>
{{ mr.targetBranch }}
</a>
</p>
</section>
</div>
</div>
</template>
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetConflicts',
props: {
mr: { type: Object, required: true },
},
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true" />
<div class="media-body space-children">
<span
v-if="mr.shouldBeRebased"
class="bold">
Fast-forward merge is not possible.
To merge this request, first rebase locally.
</span>
<template v-else>
<span class="bold">
There are merge conflicts<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge">
Resolve these conflicts or ask someone with write access to this repository to merge it locally
</span>
</span>
<a
v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath"
class="js-resolve-conflicts-button btn btn-default btn-xs">
Resolve conflicts
</a>
<a
v-if="mr.canMerge"
class="js-merge-locally-button btn btn-default btn-xs"
data-toggle="modal"
href="#modal_merge_info">
Merge locally
</a>
</template>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetConflicts',
components: {
statusIcon,
},
props: {
/* TODO: This is providing all store and service down when it
only needs a few props */
mr: {
type: Object,
required: true,
default: () => ({}),
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span
v-if="mr.shouldBeRebased"
class="bold"
>
{{ s__(`mrWidget|Fast-forward merge is not possible.
To merge this request, first rebase locally.`) }}
</span>
<template v-else>
<span class="bold">
{{ s__("mrWidget|There are merge conflicts") }}<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge">
{{ s__(`mrWidget|Resolve these conflicts or ask someone
with write access to this repository to merge it locally`) }}
</span>
</span>
<a
v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath"
class="js-resolve-conflicts-button btn btn-default btn-xs"
>
{{ s__("mrWidget|Resolve conflicts") }}
</a>
<button
v-if="mr.canMerge"
class="js-merge-locally-button btn btn-default btn-xs"
data-toggle="modal"
data-target="#modal_merge_info"
>
{{ s__("mrWidget|Merge locally") }}
</button>
</template>
</div>
</div>
</template>
import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetFailedToMerge',
props: {
mr: { type: Object, required: true },
},
data() {
return {
timer: 10,
isRefreshing: false,
};
},
mounted() {
setInterval(() => {
this.updateTimer();
}, 1000);
},
created() {
eventHub.$emit('DisablePolling');
},
computed: {
timerText() {
return this.timer > 1 ? `${this.timer} seconds` : 'a second';
},
},
methods: {
refresh() {
this.isRefreshing = true;
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('EnablePolling');
},
updateTimer() {
this.timer = this.timer - 1;
if (this.timer === 0) {
this.refresh();
}
},
},
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<template v-if="isRefreshing">
<status-icon status="loading" />
<span class="media-body bold js-refresh-label">
Refreshing now
</span>
</template>
<template v-else>
<status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
<span
class="has-error-message"
v-if="mr.mergeError">
{{mr.mergeError}}.
</span>
<span v-else>Merge failed.</span>
<span
:class="{ 'has-custom-error': mr.mergeError }">
Refreshing in {{timerText}} to show the updated status...
</span>
</span>
<button
@click="refresh"
class="btn btn-default btn-xs js-refresh-button"
type="button">
Refresh now
</button>
</div>
</template>
</div>
`,
};
<script>
import { n__ } from '~/locale';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetFailedToMerge',
components: {
statusIcon,
},
props: {
mr: {
type: Object,
required: true,
default: () => ({}),
},
},
data() {
return {
timer: 10,
isRefreshing: false,
};
},
computed: {
timerText() {
return n__(
'Refreshing in a second to show the updated status...',
'Refreshing in %d seconds to show the updated status...',
this.timer,
);
},
},
mounted() {
setInterval(() => {
this.updateTimer();
}, 1000);
},
created() {
eventHub.$emit('DisablePolling');
},
methods: {
refresh() {
this.isRefreshing = true;
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('EnablePolling');
},
updateTimer() {
this.timer = this.timer - 1;
if (this.timer === 0) {
this.refresh();
}
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<template v-if="isRefreshing">
<status-icon status="loading" />
<span class="media-body bold js-refresh-label">
{{ s__("mrWidget|Refreshing now") }}
</span>
</template>
<template v-else>
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
<span
class="has-error-message"
v-if="mr.mergeError"
>
{{ mr.mergeError }}.
</span>
<span v-else>
{{ s__("mrWidget|Merge failed.") }}
</span>
<span
:class="{ 'has-custom-error': mr.mergeError }"
>
{{ timerText }}
</span>
</span>
<button
@click="refresh"
class="btn btn-default btn-xs js-refresh-button"
type="button"
>
{{ s__("mrWidget|Refresh now") }}
</button>
</div>
</template>
</div>
</template>
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon';
import MRWidgetAuthor from '../../components/mr_widget_author';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetMergeWhenPipelineSucceeds',
props: {
mr: { type: Object, required: true },
service: { type: Object, required: true },
},
components: {
'mr-widget-author': MRWidgetAuthor,
statusIcon,
},
data() {
return {
isCancellingAutoMerge: false,
isRemovingSourceBranch: false,
};
},
computed: {
canRemoveSourceBranch() {
const { shouldRemoveSourceBranch, canRemoveSourceBranch,
mergeUserId, currentUserId } = this.mr;
return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId;
},
},
methods: {
cancelAutomaticMerge() {
this.isCancellingAutoMerge = true;
this.service.cancelAutomaticMerge()
.then(res => res.data)
.then((data) => {
eventHub.$emit('UpdateWidgetData', data);
})
.catch(() => {
this.isCancellingAutoMerge = false;
new Flash('Something went wrong. Please try again.'); // eslint-disable-line
});
},
removeSourceBranch() {
const options = {
sha: this.mr.sha,
merge_when_pipeline_succeeds: true,
should_remove_source_branch: true,
};
this.isRemovingSourceBranch = true;
this.service.mergeResource.save(options)
.then(res => res.data)
.then((data) => {
if (data.status === 'merge_when_pipeline_succeeds') {
eventHub.$emit('MRWidgetUpdateRequested');
}
})
.catch(() => {
this.isRemovingSourceBranch = false;
new Flash('Something went wrong. Please try again.'); // eslint-disable-line
});
},
},
template: `
<div class="mr-widget-body media">
<status-icon status="success" />
<div class="media-body">
<h4 class="flex-container-block">
<span class="append-right-10">
Set by
<mr-widget-author :author="mr.setToMWPSBy" />
to be merged automatically when the pipeline succeeds
</span>
<a
v-if="mr.canCancelAutomaticMerge"
@click.prevent="cancelAutomaticMerge"
:disabled="isCancellingAutoMerge"
role="button"
href="#"
class="btn btn-xs btn-default js-cancel-auto-merge">
<i
v-if="isCancellingAutoMerge"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
Cancel automatic merge
</a>
</h4>
<section class="mr-info-list">
<p>The changes will be merged into
<a
:href="mr.targetBranchPath"
class="label-branch">
{{mr.targetBranch}}
</a>
</p>
<p v-if="mr.shouldRemoveSourceBranch">
The source branch will be removed
</p>
<p
v-else
class="flex-container-block"
>
<span class="append-right-10">
The source branch will not be removed
</span>
<a
v-if="canRemoveSourceBranch"
:disabled="isRemovingSourceBranch"
@click.prevent="removeSourceBranch"
role="button"
class="btn btn-xs btn-default js-remove-source-branch"
href="#">
<i
v-if="isRemovingSourceBranch"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
Remove source branch
</a>
</p>
</section>
</div>
</div>
`,
};
<script>
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import mrWidgetAuthor from '../../components/mr_widget_author';
import eventHub from '../../event_hub';
export default {
name: 'MRWidgetMergeWhenPipelineSucceeds',
components: {
mrWidgetAuthor,
statusIcon,
},
props: {
mr: {
type: Object,
required: true,
default: () => ({}),
},
service: {
type: Object,
required: true,
default: () => ({}),
},
},
data() {
return {
isCancellingAutoMerge: false,
isRemovingSourceBranch: false,
};
},
computed: {
canRemoveSourceBranch() {
const {
shouldRemoveSourceBranch,
canRemoveSourceBranch,
mergeUserId,
currentUserId,
} = this.mr;
return !shouldRemoveSourceBranch &&
canRemoveSourceBranch &&
mergeUserId === currentUserId;
},
},
methods: {
cancelAutomaticMerge() {
this.isCancellingAutoMerge = true;
this.service.cancelAutomaticMerge()
.then(res => res.data)
.then((data) => {
eventHub.$emit('UpdateWidgetData', data);
})
.catch(() => {
this.isCancellingAutoMerge = false;
Flash('Something went wrong. Please try again.');
});
},
removeSourceBranch() {
const options = {
sha: this.mr.sha,
merge_when_pipeline_succeeds: true,
should_remove_source_branch: true,
};
this.isRemovingSourceBranch = true;
this.service.mergeResource.save(options)
.then(res => res.data)
.then((data) => {
if (data.status === 'merge_when_pipeline_succeeds') {
eventHub.$emit('MRWidgetUpdateRequested');
}
})
.catch(() => {
this.isRemovingSourceBranch = false;
Flash('Something went wrong. Please try again.');
});
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon status="success" />
<div class="media-body">
<h4 class="flex-container-block">
<span class="append-right-10">
{{ s__("mrWidget|Set by") }}
<mr-widget-author :author="mr.setToMWPSBy" />
{{ s__("mrWidget|to be merged automatically when the pipeline succeeds") }}
</span>
<a
v-if="mr.canCancelAutomaticMerge"
@click.prevent="cancelAutomaticMerge"
:disabled="isCancellingAutoMerge"
role="button"
href="#"
class="btn btn-xs btn-default js-cancel-auto-merge">
<i
v-if="isCancellingAutoMerge"
class="fa fa-spinner fa-spin"
aria-hidden="true"
>
</i>
{{ s__("mrWidget|Cancel automatic merge") }}
</a>
</h4>
<section class="mr-info-list">
<p>
{{ s__("mrWidget|The changes will be merged into") }}
<a
:href="mr.targetBranchPath"
class="label-branch"
>
{{ mr.targetBranch }}
</a>
</p>
<p v-if="mr.shouldRemoveSourceBranch">
{{ s__("mrWidget|The source branch will be removed") }}
</p>
<p
v-else
class="flex-container-block"
>
<span class="append-right-10">
{{ s__("mrWidget|The source branch will not be removed") }}
</span>
<a
v-if="canRemoveSourceBranch"
:disabled="isRemovingSourceBranch"
@click.prevent="removeSourceBranch"
role="button"
class="btn btn-xs btn-default js-remove-source-branch"
href="#"
>
<i
v-if="isRemovingSourceBranch"
class="fa fa-spinner fa-spin"
aria-hidden="true"
>
</i>
{{ s__("mrWidget|Remove source branch") }}
</a>
</p>
</section>
</div>
</div>
</template>
...@@ -2,7 +2,7 @@ import Flash from '../../../flash'; ...@@ -2,7 +2,7 @@ import Flash from '../../../flash';
import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
......
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetMerging',
props: {
mr: { type: Object, required: true },
},
components: {
statusIcon,
},
template: `
<div class="mr-widget-body mr-state-locked media">
<status-icon status="loading" />
<div class="media-body">
<h4>
This merge request is in the process of being merged
</h4>
<section class="mr-info-list">
<p>
The changes will be merged into
<span class="label-branch">
<a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
</span>
</p>
</section>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetMerging',
components: {
statusIcon,
},
props: {
mr: {
type: Object,
required: true,
default: () => ({}),
},
},
};
</script>
<template>
<div class="mr-widget-body mr-state-locked media">
<status-icon status="loading" />
<div class="media-body">
<h4>
{{ s__("mrWidget|This merge request is in the process of being merged") }}
</h4>
<section class="mr-info-list">
<p>
{{ s__("mrWidget|The changes will be merged into") }}
<span class="label-branch">
<a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a>
</span>
</p>
</section>
</div>
</div>
</template>
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
import mrWidgetMergeHelp from '../../components/mr_widget_merge_help'; import mrWidgetMergeHelp from '../../components/mr_widget_merge_help';
......
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetNotAllowed', name: 'MRWidgetNotAllowed',
......
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetPipelineBlocked', name: 'MRWidgetPipelineBlocked',
......
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetPipelineBlocked', name: 'MRWidgetPipelineBlocked',
......
...@@ -3,7 +3,7 @@ import warningSvg from 'icons/_icon_status_warning.svg'; ...@@ -3,7 +3,7 @@ import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll'; import simplePoll from '~/lib/utils/simple_poll';
import MergeRequest from '../../../merge_request'; import MergeRequest from '../../../merge_request';
import Flash from '../../../flash'; import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
......
<script> <script>
import simplePoll from '../../../lib/utils/simple_poll'; import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Flash from '../../../flash'; import Flash from '../../../flash';
......
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetSHAMismatch', name: 'MRWidgetSHAMismatch',
......
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetUnresolvedDiscussions', name: 'MRWidgetUnresolvedDiscussions',
......
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
......
...@@ -17,12 +17,12 @@ export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue'; ...@@ -17,12 +17,12 @@ export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
export { default as WidgetDeployment } from './components/mr_widget_deployment'; export { default as WidgetDeployment } from './components/mr_widget_deployment';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links'; export { default as WidgetRelatedLinks } from './components/mr_widget_related_links';
export { default as MergedState } from './components/states/mr_widget_merged'; export { default as MergedState } from './components/states/mr_widget_merged';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
export { default as ClosedState } from './components/states/mr_widget_closed'; export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging'; export { default as MergingState } from './components/states/mr_widget_merging.vue';
export { default as WipState } from './components/states/mr_widget_wip'; export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
...@@ -31,10 +31,10 @@ export { default as SHAMismatchState } from './components/states/mr_widget_sha_m ...@@ -31,10 +31,10 @@ export { default as SHAMismatchState } from './components/states/mr_widget_sha_m
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions'; export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds'; export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue'; export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue'; export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue';
export { default as CheckingState } from './components/states/mr_widget_checking'; export { default as CheckingState } from './components/states/mr_widget_checking.vue';
export { default as MRWidgetStore } from './stores/mr_widget_store'; export { default as MRWidgetStore } from './stores/mr_widget_store';
export { default as MRWidgetService } from './services/mr_widget_service'; export { default as MRWidgetService } from './services/mr_widget_service';
export { default as eventHub } from './event_hub'; export { default as eventHub } from './event_hub';
......
...@@ -26,15 +26,15 @@ ...@@ -26,15 +26,15 @@
} }
.ci-status-icon-canceled, .ci-status-icon-canceled,
.ci-status-icon-disabled, .ci-status-icon-disabled {
.ci-status-icon-not-found {
svg { svg {
fill: $gl-text-color; fill: $gl-text-color;
} }
} }
.ci-status-icon-created, .ci-status-icon-created,
.ci-status-icon-skipped { .ci-status-icon-skipped,
.ci-status-icon-notfound {
svg { svg {
fill: $gray-darkest; fill: $gray-darkest;
} }
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
width: 100%; width: 100%;
} }
$image-widths: 250 306 394; $image-widths: 250 306 394 430;
@each $width in $image-widths { @each $width in $image-widths {
&.svg-#{$width} { &.svg-#{$width} {
img, img,
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
.modal-body { .modal-body {
background-color: $modal-body-bg; background-color: $modal-body-bg;
line-height: $line-height-base;
min-height: $modal-body-height; min-height: $modal-body-height;
position: relative; position: relative;
padding: #{3 * $grid-size} #{2 * $grid-size}; padding: #{3 * $grid-size} #{2 * $grid-size};
......
...@@ -310,8 +310,8 @@ ...@@ -310,8 +310,8 @@
} }
&.invalid { &.invalid {
@include status-color($gray-dark, $gray, $common-gray-dark); @include status-color($gray-dark, $gray, $gray-darkest);
border-color: $common-gray-light; border-color: $gray-darkest;
} }
} }
...@@ -335,8 +335,8 @@ ...@@ -335,8 +335,8 @@
&.invalid { &.invalid {
svg { svg {
border: 1px solid $common-gray-light; border: 1px solid $gray-darkest;
fill: $common-gray-light; fill: $gray-darkest;
} }
} }
......
...@@ -147,6 +147,8 @@ class ApplicationController < ActionController::Base ...@@ -147,6 +147,8 @@ class ApplicationController < ActionController::Base
format.html do format.html do
render file: Rails.root.join("public", "404"), layout: false, status: "404" render file: Rails.root.join("public", "404"), layout: false, status: "404"
end end
# Prevent the Rails CSRF protector from thinking a missing .js file is a JavaScript file
format.js { render json: '', status: :not_found, content_type: 'application/json' }
format.any { head :not_found } format.any { head :not_found }
end end
end end
......
...@@ -2,7 +2,11 @@ module GroupTree ...@@ -2,7 +2,11 @@ module GroupTree
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_group_tree(groups) def render_group_tree(groups)
@groups = if params[:filter].present? @groups = if params[:filter].present?
Gitlab::GroupHierarchy.new(groups.search(params[:filter])) # We find the ancestors by ID of the search results here.
# Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded.
group_ids = groups.search(params[:filter]).select(:id)
Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors .base_and_ancestors
else else
# Only show root groups if no parent-id is given # Only show root groups if no parent-id is given
......
...@@ -8,7 +8,8 @@ class HealthController < ActionController::Base ...@@ -8,7 +8,8 @@ class HealthController < ActionController::Base
Gitlab::HealthChecks::Redis::CacheCheck, Gitlab::HealthChecks::Redis::CacheCheck,
Gitlab::HealthChecks::Redis::QueuesCheck, Gitlab::HealthChecks::Redis::QueuesCheck,
Gitlab::HealthChecks::Redis::SharedStateCheck, Gitlab::HealthChecks::Redis::SharedStateCheck,
Gitlab::HealthChecks::FsShardsCheck Gitlab::HealthChecks::FsShardsCheck,
Gitlab::HealthChecks::GitalyCheck
].freeze ].freeze
def readiness def readiness
......
...@@ -83,7 +83,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -83,7 +83,7 @@ class Projects::MilestonesController < Projects::ApplicationController
Milestones::DestroyService.new(project, current_user).execute(milestone) Milestones::DestroyService.new(project, current_user).execute(milestone)
respond_to do |format| respond_to do |format|
format.html { redirect_to namespace_project_milestones_path, status: 302 } format.html { redirect_to namespace_project_milestones_path, status: 303 }
format.js { head :ok } format.js { head :ok }
end end
end end
......
...@@ -27,12 +27,16 @@ class GroupDescendantsFinder ...@@ -27,12 +27,16 @@ class GroupDescendantsFinder
end end
def execute def execute
# The children array might be extended with the ancestors of projects when # The children array might be extended with the ancestors of projects and
# filtering. In that case, take the maximum so the array does not get limited # subgroups when filtering. In that case, take the maximum so the array does
# Otherwise, allow paginating through all results # not get limited otherwise, allow paginating through all results.
# #
all_required_elements = children all_required_elements = children
all_required_elements |= ancestors_for_projects if params[:filter] if params[:filter]
all_required_elements |= ancestors_of_filtered_subgroups
all_required_elements |= ancestors_of_filtered_projects
end
total_count = [all_required_elements.size, paginator.total_count].max total_count = [all_required_elements.size, paginator.total_count].max
Kaminari.paginate_array(all_required_elements, total_count: total_count) Kaminari.paginate_array(all_required_elements, total_count: total_count)
...@@ -49,8 +53,11 @@ class GroupDescendantsFinder ...@@ -49,8 +53,11 @@ class GroupDescendantsFinder
end end
def paginator def paginator
@paginator ||= Gitlab::MultiCollectionPaginator.new(subgroups, projects, @paginator ||= Gitlab::MultiCollectionPaginator.new(
per_page: params[:per_page]) subgroups,
projects.with_route,
per_page: params[:per_page]
)
end end
def direct_child_groups def direct_child_groups
...@@ -94,15 +101,21 @@ class GroupDescendantsFinder ...@@ -94,15 +101,21 @@ class GroupDescendantsFinder
# #
# So when searching 'project', on the 'subgroup' page we want to preload # So when searching 'project', on the 'subgroup' page we want to preload
# 'nested-group' but not 'subgroup' or 'root' # 'nested-group' but not 'subgroup' or 'root'
def ancestors_for_groups(base_for_ancestors) def ancestors_of_groups(base_for_ancestors)
Gitlab::GroupHierarchy.new(base_for_ancestors) group_ids = base_for_ancestors.except(:select, :sort).select(:id)
Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors(upto: parent_group.id) .base_and_ancestors(upto: parent_group.id)
end end
def ancestors_for_projects def ancestors_of_filtered_projects
projects_to_load_ancestors_of = projects.where.not(namespace: parent_group) projects_to_load_ancestors_of = projects.where.not(namespace: parent_group)
groups_to_load_ancestors_of = Group.where(id: projects_to_load_ancestors_of.select(:namespace_id)) groups_to_load_ancestors_of = Group.where(id: projects_to_load_ancestors_of.select(:namespace_id))
ancestors_for_groups(groups_to_load_ancestors_of) ancestors_of_groups(groups_to_load_ancestors_of)
.with_selects_for_list(archived: params[:archived])
end
def ancestors_of_filtered_subgroups
ancestors_of_groups(subgroups)
.with_selects_for_list(archived: params[:archived]) .with_selects_for_list(archived: params[:archived])
end end
...@@ -112,7 +125,7 @@ class GroupDescendantsFinder ...@@ -112,7 +125,7 @@ class GroupDescendantsFinder
# When filtering subgroups, we want to find all matches withing the tree of # When filtering subgroups, we want to find all matches withing the tree of
# descendants to show to the user # descendants to show to the user
groups = if params[:filter] groups = if params[:filter]
ancestors_for_groups(subgroups_matching_filter) subgroups_matching_filter
else else
direct_child_groups direct_child_groups
end end
...@@ -121,8 +134,10 @@ class GroupDescendantsFinder ...@@ -121,8 +134,10 @@ class GroupDescendantsFinder
end end
def direct_child_projects def direct_child_projects
GroupProjectsFinder.new(group: parent_group, current_user: current_user, params: params) GroupProjectsFinder.new(group: parent_group,
.execute current_user: current_user,
options: { only_owned: true },
params: params).execute
end end
# Finds all projects nested under `parent_group` or any of its descendant # Finds all projects nested under `parent_group` or any of its descendant
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# label_name: string # label_name: string
# sort: string # sort: string
# my_reaction_emoji: string # my_reaction_emoji: string
# public_only: boolean
# #
class IssuesFinder < IssuableFinder class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
...@@ -40,7 +41,15 @@ class IssuesFinder < IssuableFinder ...@@ -40,7 +41,15 @@ class IssuesFinder < IssuableFinder
private private
def init_collection def init_collection
with_confidentiality_access_check if public_only?
Issue.public_only
else
with_confidentiality_access_check
end
end
def public_only?
params.fetch(:public_only, false)
end end
def user_can_see_all_confidential_issues? def user_can_see_all_confidential_issues?
......
...@@ -14,13 +14,13 @@ module AutoDevopsHelper ...@@ -14,13 +14,13 @@ module AutoDevopsHelper
if missing_service if missing_service
params = { params = {
kubernetes: link_to('Kubernetes service', edit_project_service_path(project, 'kubernetes')) kubernetes: link_to('Kubernetes cluster', project_clusters_path(project))
} }
if missing_domain if missing_domain
_('Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly.') % params _('Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly.') % params
else else
_('Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly.') % params _('Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly.') % params
end end
elsif missing_domain elsif missing_domain
_('Auto Review Apps and Auto Deploy need a domain name to work correctly.') _('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
......
...@@ -170,4 +170,8 @@ module SearchHelper ...@@ -170,4 +170,8 @@ module SearchHelper
# Truncato's filtered_tags and filtered_attributes are not quite the same # Truncato's filtered_tags and filtered_attributes are not quite the same
sanitize(html, tags: %w(a p ol ul li pre code)) sanitize(html, tags: %w(a p ol ul li pre code))
end end
def limited_count(count, limit = 1000)
count > limit ? "#{limit}+" : count
end
end end
...@@ -2,7 +2,7 @@ require 'webpack/rails/manifest' ...@@ -2,7 +2,7 @@ require 'webpack/rails/manifest'
module WebpackHelper module WebpackHelper
def webpack_bundle_tag(bundle, force_same_domain: false) def webpack_bundle_tag(bundle, force_same_domain: false)
javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: true)) javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: force_same_domain))
end end
# override webpack-rails gem helper until changes can make it upstream # override webpack-rails gem helper until changes can make it upstream
......
...@@ -524,7 +524,7 @@ module Ci ...@@ -524,7 +524,7 @@ module Ci
return unless sha return unless sha
project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path) project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
rescue GRPC::NotFound, Rugged::ReferenceError, GRPC::Internal rescue GRPC::NotFound, GRPC::Internal
nil nil
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment