Commit c02d344c authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into ide-staged-changes

parents 49f198e6 32d2206b
...@@ -78,6 +78,19 @@ stages: ...@@ -78,6 +78,19 @@ stages:
- mysql:latest - mysql:latest
- redis:alpine - redis:alpine
.rails5-variables: &rails5-variables
script:
- export RAILS5=${RAILS5}
- export BUNDLE_GEMFILE=${BUNDLE_GEMFILE}
.rails5: &rails5
allow_failure: true
only:
- /rails5/
variables:
BUNDLE_GEMFILE: "Gemfile.rails5"
RAILS5: "true"
# Skip all jobs except the ones that begin with 'docs/'. # Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes. # Used for commits including ONLY documentation changes.
# https://docs.gitlab.com/ce/development/writing_documentation.html#testing # https://docs.gitlab.com/ce/development/writing_documentation.html#testing
...@@ -118,6 +131,7 @@ stages: ...@@ -118,6 +131,7 @@ stages:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
<<: *pull-cache <<: *pull-cache
<<: *rails5-variables
stage: test stage: test
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
...@@ -148,14 +162,23 @@ stages: ...@@ -148,14 +162,23 @@ stages:
<<: *rspec-metadata <<: *rspec-metadata
<<: *use-pg <<: *use-pg
.rspec-metadata-pg-rails5: &rspec-metadata-pg-rails5
<<: *rspec-metadata-pg
<<: *rails5
.rspec-metadata-mysql: &rspec-metadata-mysql .rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata <<: *rspec-metadata
<<: *use-mysql <<: *use-mysql
.rspec-metadata-mysql-rails5: &rspec-metadata-mysql-rails5
<<: *rspec-metadata-mysql
<<: *rails5
.spinach-metadata: &spinach-metadata .spinach-metadata: &spinach-metadata
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
<<: *pull-cache <<: *pull-cache
<<: *rails5-variables
stage: test stage: test
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
...@@ -179,10 +202,18 @@ stages: ...@@ -179,10 +202,18 @@ stages:
<<: *spinach-metadata <<: *spinach-metadata
<<: *use-pg <<: *use-pg
.spinach-metadata-pg-rails5: &spinach-metadata-pg-rails5
<<: *spinach-metadata-pg
<<: *rails5
.spinach-metadata-mysql: &spinach-metadata-mysql .spinach-metadata-mysql: &spinach-metadata-mysql
<<: *spinach-metadata <<: *spinach-metadata
<<: *use-mysql <<: *use-mysql
.spinach-metadata-mysql-rails5: &spinach-metadata-mysql-rails5
<<: *spinach-metadata-mysql
<<: *rails5
.only-canonical-masters: &only-canonical-masters .only-canonical-masters: &only-canonical-masters
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
...@@ -266,12 +297,13 @@ package-and-qa: ...@@ -266,12 +297,13 @@ package-and-qa:
when: manual when: manual
variables: variables:
GIT_STRATEGY: none GIT_STRATEGY: none
retry: 0
before_script: before_script:
# We need to download the script rather than clone the repo since the # We need to download the script rather than clone the repo since the
# package-and-qa job will not be able to run when the branch gets # package-and-qa job will not be able to run when the branch gets
# deleted (when merging the MR). # deleted (when merging the MR).
- apk add --update openssl - apk add --update openssl
- wget https://gitlab.com/gitlab-org/gitlab-ce/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus - wget https://gitlab.com/$CI_PROJECT_PATH/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus
- chmod 755 trigger-build-omnibus - chmod 755 trigger-build-omnibus
script: script:
- ./trigger-build-omnibus - ./trigger-build-omnibus
...@@ -467,6 +499,70 @@ spinach-pg 1 2: *spinach-metadata-pg ...@@ -467,6 +499,70 @@ spinach-pg 1 2: *spinach-metadata-pg
spinach-mysql 0 2: *spinach-metadata-mysql spinach-mysql 0 2: *spinach-metadata-mysql
spinach-mysql 1 2: *spinach-metadata-mysql spinach-mysql 1 2: *spinach-metadata-mysql
rspec-pg-rails5 0 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 1 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 2 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 3 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 4 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 5 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 6 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 7 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 8 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 9 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 10 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 11 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 12 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 13 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 14 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 15 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 16 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 17 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 18 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 19 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 20 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 21 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 22 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 23 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 24 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 25 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 26 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 27 28: *rspec-metadata-pg-rails5
rspec-mysql-rails5 0 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 1 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 2 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 3 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 4 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 5 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 6 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 7 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 8 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 9 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 10 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 11 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 12 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 13 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 14 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 15 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 16 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 17 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 18 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 19 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 20 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 21 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 22 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 23 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 24 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 25 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 26 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 27 28: *rspec-metadata-mysql-rails5
spinach-pg-rails5 0 2: *spinach-metadata-pg-rails5
spinach-pg-rails5 1 2: *spinach-metadata-pg-rails5
spinach-mysql-rails5 0 2: *spinach-metadata-mysql-rails5
spinach-mysql-rails5 1 2: *spinach-metadata-mysql-rails5
static-analysis: static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-no-db-pull-cache-job
dependencies: dependencies:
...@@ -617,21 +713,26 @@ karma: ...@@ -617,21 +713,26 @@ karma:
codequality: codequality:
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-no-db-pull-cache-job
image: docker:latest image: docker:stable
allow_failure: true
# gitlab-org runners set `privileged: false` but we need to have it set to true
# since we're using Docker in Docker
tags: []
before_script: [] before_script: []
services: services:
- docker:dind - docker:dind
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
CODECLIMATE_FORMAT: json
cache: {} cache: {}
dependencies: [] dependencies: []
script: script:
- apk update && apk add jq # Get the custom rubocop codeclimate image (https://gitlab.com/gitlab-org/codeclimate-rubocop/wikis/home)
- ./scripts/codequality analyze -f json > raw_codeclimate.json || true - docker pull dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1
# The following line keeps only the fields used in the MR widget, reducing the JSON artifact size - docker tag dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1 codeclimate/codeclimate-rubocop:gitlab-codeclimate-rubocop-0-52-1
- jq -c 'map({check_name,description,fingerprint,location})' raw_codeclimate.json > codeclimate.json # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts: artifacts:
paths: [codeclimate.json] paths: [codeclimate.json]
expire_in: 1 week expire_in: 1 week
......
...@@ -33,13 +33,16 @@ When removing columns, tables, indexes or other structures: ...@@ -33,13 +33,16 @@ When removing columns, tables, indexes or other structures:
## General Checklist ## General Checklist
- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added, if necessary - [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md) - [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html)
- [ ] API support added - [ ] API support added
- [ ] Tests added for this feature/bug - [ ] Tests added for this feature/bug
- Review - Review
- [ ] Has been reviewed by Backend - [ ] Has been reviewed by Backend
- [ ] Has been reviewed by Database - [ ] Has been reviewed by Database
- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html) - [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides) - [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) - [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
- [ ] Internationalization required/considered
- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan
- [ ] End-to-end tests pass (`package-qa` manual pipeline job)
...@@ -191,7 +191,6 @@ entry. ...@@ -191,7 +191,6 @@ entry.
- Enable privileged mode for GitLab Runner. !17528 - Enable privileged mode for GitLab Runner. !17528
- Expose GITLAB_FEATURES as CI/CD variable (fixes #40994). - Expose GITLAB_FEATURES as CI/CD variable (fixes #40994).
- Upgrade GitLab Workhorse to 4.0.0. - Upgrade GitLab Workhorse to 4.0.0.
- Allow CI/CD Jobs being grouped on version strings.
- Add discussions API for Issues and Snippets. - Add discussions API for Issues and Snippets.
- Add one group board to Libre. - Add one group board to Libre.
- Add support for filtering by source and target branch to merge requests API. - Add support for filtering by source and target branch to merge requests API.
......
...@@ -384,7 +384,7 @@ group :test do ...@@ -384,7 +384,7 @@ group :test do
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.8.0' gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2' gem 'webmock', '~> 2.3.2'
gem 'test_after_commit', '~> 1.1' gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0.
gem 'sham_rack', '~> 1.3.6' gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.0.5' gem 'concurrent-ruby', '~> 1.0.5'
gem 'test-prof', '~> 0.2.5' gem 'test-prof', '~> 0.2.5'
......
...@@ -60,7 +60,7 @@ GEM ...@@ -60,7 +60,7 @@ GEM
faraday_middleware-multi_json (~> 0.0) faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0) oauth2 (~> 1.0)
asciidoctor (1.5.6.1) asciidoctor (1.5.6.1)
asciidoctor-plantuml (0.0.7) asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
asset_sync (2.2.0) asset_sync (2.2.0)
activemodel (>= 4.1.0) activemodel (>= 4.1.0)
...@@ -97,7 +97,7 @@ GEM ...@@ -97,7 +97,7 @@ GEM
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
bootstrap_form (2.7.0) bootstrap_form (2.7.0)
brakeman (3.6.2) brakeman (4.2.1)
browser (2.5.3) browser (2.5.3)
builder (3.2.3) builder (3.2.3)
bullet (5.5.1) bullet (5.5.1)
...@@ -144,6 +144,7 @@ GEM ...@@ -144,6 +144,7 @@ GEM
connection_pool (2.2.1) connection_pool (2.2.1)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.3)
creole (0.5.0) creole (0.5.0)
css_parser (1.6.0) css_parser (1.6.0)
addressable addressable
...@@ -244,10 +245,11 @@ GEM ...@@ -244,10 +245,11 @@ GEM
builder builder
excon (~> 0.58) excon (~> 0.58)
formatador (~> 0.2) formatador (~> 0.2)
fog-google (0.6.0) fog-google (1.3.3)
fog-core fog-core
fog-json fog-json
fog-xml fog-xml
google-api-client (~> 0.19.1)
fog-json (1.0.2) fog-json (1.0.2)
fog-core (~> 1.0) fog-core (~> 1.0)
multi_json (~> 1.10) multi_json (~> 1.10)
...@@ -289,7 +291,7 @@ GEM ...@@ -289,7 +291,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.88.0) gitaly-proto (0.91.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -398,7 +400,7 @@ GEM ...@@ -398,7 +400,7 @@ GEM
hipchat (1.5.4) hipchat (1.5.4)
httparty httparty
mimemagic mimemagic
html-pipeline (2.6.0) html-pipeline (2.7.1)
activesupport (>= 2) activesupport (>= 2)
nokogiri (>= 1.4) nokogiri (>= 1.4)
html2text (0.2.1) html2text (0.2.1)
...@@ -484,7 +486,8 @@ GEM ...@@ -484,7 +486,8 @@ GEM
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.0.3) loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.0) mail (2.7.0)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
...@@ -527,8 +530,8 @@ GEM ...@@ -527,8 +530,8 @@ GEM
omniauth (1.8.1) omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0) hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.2) omniauth-auth0 (2.0.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.4)
omniauth-authentiq (0.3.1) omniauth-authentiq (0.3.1)
omniauth-oauth2 (~> 1.3, >= 1.3.1) omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.9) omniauth-azure-oauth2 (0.0.9)
...@@ -551,6 +554,9 @@ GEM ...@@ -551,6 +554,9 @@ GEM
jwt (>= 1.5) jwt (>= 1.5)
omniauth (>= 1.1.1) omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5) omniauth-oauth2 (>= 1.5)
omniauth-jwt (0.0.2)
jwt
omniauth (~> 1.1)
omniauth-kerberos (0.3.0) omniauth-kerberos (0.3.0)
omniauth-multipassword omniauth-multipassword
timfel-krb5-auth (~> 0.8) timfel-krb5-auth (~> 0.8)
...@@ -569,9 +575,9 @@ GEM ...@@ -569,9 +575,9 @@ GEM
ruby-saml (~> 1.7) ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.4.0)
json (~> 1.3)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
rack
omniauth_crowd (2.2.3) omniauth_crowd (2.2.3)
activesupport activesupport
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -808,7 +814,7 @@ GEM ...@@ -808,7 +814,7 @@ GEM
rubyzip (1.2.1) rubyzip (1.2.1)
rufus-scheduler (3.4.2) rufus-scheduler (3.4.2)
et-orbi (~> 1.0) et-orbi (~> 1.0)
rugged (0.26.0) rugged (0.27.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -907,8 +913,6 @@ GEM ...@@ -907,8 +913,6 @@ GEM
sysexits (1.2.0) sysexits (1.2.0)
temple (0.7.7) temple (0.7.7)
test-prof (0.2.5) test-prof (0.2.5)
test_after_commit (1.1.0)
activerecord (>= 3.2)
text (1.3.1) text (1.3.1)
thin (1.7.2) thin (1.7.2)
daemons (~> 1.0, >= 1.0.9) daemons (~> 1.0, >= 1.0.9)
...@@ -995,8 +999,8 @@ DEPENDENCIES ...@@ -995,8 +999,8 @@ DEPENDENCIES
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
asana (~> 0.6.0) asana (~> 0.6.0)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.7) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0) asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0) attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
...@@ -1009,7 +1013,7 @@ DEPENDENCIES ...@@ -1009,7 +1013,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0) bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0) brakeman (~> 4.2)
browser (~> 2.2) browser (~> 2.2)
bullet (~> 5.5.0) bullet (~> 5.5.0)
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
...@@ -1044,9 +1048,9 @@ DEPENDENCIES ...@@ -1044,9 +1048,9 @@ DEPENDENCIES
flipper-active_record (~> 0.13.0) flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0) flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0) fog-aliyun (~> 0.2.0)
fog-aws (~> 2.0) fog-aws (~> 2.0.1)
fog-core (~> 1.44) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 1.3.3)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
...@@ -1058,7 +1062,7 @@ DEPENDENCIES ...@@ -1058,7 +1062,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.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.88.0) gitaly-proto (~> 0.91.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
...@@ -1080,7 +1084,7 @@ DEPENDENCIES ...@@ -1080,7 +1084,7 @@ DEPENDENCIES
hashie-forbidden_attributes hashie-forbidden_attributes
health_check (~> 2.6.0) health_check (~> 2.6.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 2.6.0) html-pipeline (~> 2.7.1)
html2text html2text
httparty (~> 0.13.3) httparty (~> 0.13.3)
influxdb (~> 0.2) influxdb (~> 0.2)
...@@ -1095,7 +1099,7 @@ DEPENDENCIES ...@@ -1095,7 +1099,7 @@ DEPENDENCIES
license_finder (~> 3.1) license_finder (~> 3.1)
licensee (~> 8.9) licensee (~> 8.9)
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.0.3) loofah (~> 2.2)
mail_room (~> 0.9.1) mail_room (~> 0.9.1)
method_source (~> 0.8) method_source (~> 0.8)
minitest (~> 5.7.0) minitest (~> 5.7.0)
...@@ -1107,19 +1111,20 @@ DEPENDENCIES ...@@ -1107,19 +1111,20 @@ DEPENDENCIES
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.8) octokit (~> 4.8)
omniauth (~> 1.8) omniauth (~> 1.8)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.1) omniauth-authentiq (~> 0.3.1)
omniauth-azure-oauth2 (~> 0.0.9) omniauth-azure-oauth2 (~> 0.0.9)
omniauth-cas3 (~> 1.1.4) omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0) omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.2) omniauth-google-oauth2 (~> 0.5.3)
omniauth-jwt (~> 0.0.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
peek (~> 1.0.1) peek (~> 1.0.1)
...@@ -1169,7 +1174,7 @@ DEPENDENCIES ...@@ -1169,7 +1174,7 @@ DEPENDENCIES
ruby-prof (~> 0.17.0) ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.26.0) rugged (~> 0.27)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0) scss_lint (~> 0.56.0)
...@@ -1197,7 +1202,6 @@ DEPENDENCIES ...@@ -1197,7 +1202,6 @@ DEPENDENCIES
state_machines-activerecord (~> 0.5.1) state_machines-activerecord (~> 0.5.1)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
test-prof (~> 0.2.5) test-prof (~> 0.2.5)
test_after_commit (~> 1.1)
thin (~> 1.7.0) thin (~> 1.7.0)
timecop (~> 0.8.0) timecop (~> 0.8.0)
toml-rb (~> 1.0.0) toml-rb (~> 1.0.0)
......
...@@ -4,7 +4,7 @@ import $ from 'jquery'; ...@@ -4,7 +4,7 @@ import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { __ } from './locale'; import { __ } from './locale';
import { isInIssuePage, isInMRPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils'; import { isInIssuePage, isInMRPage, isInEpicPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils';
import flash from './flash'; import flash from './flash';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
...@@ -300,7 +300,7 @@ class AwardsHandler { ...@@ -300,7 +300,7 @@ class AwardsHandler {
} }
isInVueNoteablePage() { isInVueNoteablePage() {
return isInIssuePage() || this.isVueMRDiscussions(); return isInIssuePage() || isInEpicPage() || this.isVueMRDiscussions();
} }
getVotesBlock() { getVotesBlock() {
......
...@@ -19,7 +19,7 @@ export default class BoardService { ...@@ -19,7 +19,7 @@ export default class BoardService {
} }
static generateIssuePath(boardId, id) { static generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`; return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
} }
all() { all() {
......
...@@ -26,8 +26,8 @@ export default class FilteredSearchDropdownManager { ...@@ -26,8 +26,8 @@ export default class FilteredSearchDropdownManager {
this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page; this.page = page;
this.groupsOnly = isGroup; this.groupsOnly = isGroup;
this.groupAncestor = isGroupAncestor; this.includeAncestorGroups = isGroupAncestor;
this.isGroupDecendent = isGroupDecendent; this.includeDescendantGroups = isGroupDecendent;
this.setupMapping(); this.setupMapping();
...@@ -108,7 +108,19 @@ export default class FilteredSearchDropdownManager { ...@@ -108,7 +108,19 @@ export default class FilteredSearchDropdownManager {
} }
getLabelsEndpoint() { getLabelsEndpoint() {
const endpoint = `${this.baseEndpoint}/labels.json`; let endpoint = `${this.baseEndpoint}/labels.json?`;
if (this.groupsOnly) {
endpoint = `${endpoint}only_group_labels=true&`;
}
if (this.includeAncestorGroups) {
endpoint = `${endpoint}include_ancestor_groups=true&`;
}
if (this.includeDescendantGroups) {
endpoint = `${endpoint}include_descendant_groups=true`;
}
return endpoint; return endpoint;
} }
......
...@@ -21,7 +21,7 @@ export default class FilteredSearchManager { ...@@ -21,7 +21,7 @@ export default class FilteredSearchManager {
constructor({ constructor({
page, page,
isGroup = false, isGroup = false,
isGroupAncestor = false, isGroupAncestor = true,
isGroupDecendent = false, isGroupDecendent = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys, filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters', stateFiltersSelector = '.issues-state-filters',
...@@ -86,6 +86,7 @@ export default class FilteredSearchManager { ...@@ -86,6 +86,7 @@ export default class FilteredSearchManager {
page: this.page, page: this.page,
isGroup: this.isGroup, isGroup: this.isGroup,
isGroupAncestor: this.isGroupAncestor, isGroupAncestor: this.isGroupAncestor,
isGroupDecendent: this.isGroupDecendent,
filteredSearchTokenKeys: this.filteredSearchTokenKeys, filteredSearchTokenKeys: this.filteredSearchTokenKeys,
}); });
......
...@@ -3,7 +3,6 @@ import { mapActions } from 'vuex'; ...@@ -3,7 +3,6 @@ import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import StageButton from './stage_button.vue'; import StageButton from './stage_button.vue';
import UnstageButton from './unstage_button.vue'; import UnstageButton from './unstage_button.vue';
import router from '../../ide_router';
export default { export default {
components: { components: {
...@@ -26,17 +25,17 @@ export default { ...@@ -26,17 +25,17 @@ export default {
return this.file.tempFile ? 'file-addition' : 'file-modified'; return this.file.tempFile ? 'file-addition' : 'file-modified';
}, },
iconClass() { iconClass() {
return `multi-file-${ return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
this.file.tempFile ? 'addition' : 'modified'
} append-right-8`;
}, },
}, },
methods: { methods: {
...mapActions(['updateViewer', 'stageChange', 'unstageChange']), ...mapActions(['discardFileChanges', 'updateViewer', 'openPendingTab']),
openFileInEditor() { openFileInEditor() {
this.updateViewer('diff'); return this.openPendingTab(this.file).then(changeViewer => {
if (changeViewer) {
router.push(`/project${this.file.url}`); this.updateViewer('diff');
}
});
}, },
fileAction() { fileAction() {
if (this.file.staged) { if (this.file.staged) {
......
...@@ -60,6 +60,7 @@ export default { ...@@ -60,6 +60,7 @@ export default {
v-if="activeFile" v-if="activeFile"
> >
<repo-tabs <repo-tabs
:active-file="activeFile"
:files="openFiles" :files="openFiles"
:viewer="viewer" :viewer="viewer"
:has-changes="hasChanges" :has-changes="hasChanges"
......
...@@ -21,7 +21,8 @@ export default { ...@@ -21,7 +21,8 @@ export default {
}, },
watch: { watch: {
file(oldVal, newVal) { file(oldVal, newVal) {
if (newVal.path !== this.file.path) { // Compare key to allow for files opened in review mode to be cached differently
if (newVal.key !== this.file.key) {
this.initMonaco(); this.initMonaco();
} }
}, },
...@@ -70,7 +71,7 @@ export default { ...@@ -70,7 +71,7 @@ export default {
}) })
.then(() => { .then(() => {
const viewerPromise = this.delayViewerUpdated const viewerPromise = this.delayViewerUpdated
? this.updateViewer('editor') ? this.updateViewer(this.file.pending ? 'diff' : 'editor')
: Promise.resolve(); : Promise.resolve();
return viewerPromise; return viewerPromise;
......
...@@ -62,11 +62,7 @@ export default { ...@@ -62,11 +62,7 @@ export default {
this.toggleTreeOpen(this.file.path); this.toggleTreeOpen(this.file.path);
} }
const delayPromise = this.file.changed return this.updateDelayViewerUpdated(true).then(() => {
? Promise.resolve()
: this.updateDelayViewerUpdated(true);
return delayPromise.then(() => {
router.push(`/project${this.file.url}`); router.push(`/project${this.file.url}`);
}); });
}, },
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import fileIcon from '~/vue_shared/components/file_icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue';
import icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import fileStatusIcon from './repo_file_status_icon.vue'; import FileStatusIcon from './repo_file_status_icon.vue';
import changedFileIcon from './changed_file_icon.vue'; import ChangedFileIcon from './changed_file_icon.vue';
export default { export default {
components: { components: {
fileStatusIcon, FileStatusIcon,
fileIcon, FileIcon,
icon, Icon,
changedFileIcon, ChangedFileIcon,
}, },
props: { props: {
tab: { tab: {
...@@ -32,7 +32,7 @@ export default { ...@@ -32,7 +32,7 @@ export default {
return `Close ${this.tab.name}`; return `Close ${this.tab.name}`;
}, },
showChangedIcon() { showChangedIcon() {
return this.fileHasChanged ? !this.tabMouseOver : false; return this.tab.changed ? !this.tabMouseOver : false;
}, },
fileHasChanged() { fileHasChanged() {
return this.tab.changed || this.tab.tempFile || this.tab.staged; return this.tab.changed || this.tab.tempFile || this.tab.staged;
...@@ -40,9 +40,15 @@ export default { ...@@ -40,9 +40,15 @@ export default {
}, },
methods: { methods: {
...mapActions(['closeFile']), ...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
clickFile(tab) { clickFile(tab) {
this.$router.push(`/project${tab.url}`); this.updateDelayViewerUpdated(true);
if (tab.pending) {
this.openPendingTab(tab);
} else {
this.$router.push(`/project${tab.url}`);
}
}, },
mouseOverTab() { mouseOverTab() {
if (this.fileHasChanged) { if (this.fileHasChanged) {
...@@ -67,7 +73,7 @@ export default { ...@@ -67,7 +73,7 @@ export default {
<button <button
type="button" type="button"
class="multi-file-tab-close" class="multi-file-tab-close"
@click.stop.prevent="closeFile(tab.path)" @click.stop.prevent="closeFile(tab)"
:aria-label="closeLabel" :aria-label="closeLabel"
> >
<icon <icon
...@@ -83,7 +89,9 @@ export default { ...@@ -83,7 +89,9 @@ export default {
<div <div
class="multi-file-tab" class="multi-file-tab"
:class="{active : tab.active }" :class="{
active: tab.active
}"
:title="tab.url" :title="tab.url"
> >
<file-icon <file-icon
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import RepoTab from './repo_tab.vue'; import RepoTab from './repo_tab.vue';
import EditorMode from './editor_mode_dropdown.vue'; import EditorMode from './editor_mode_dropdown.vue';
import router from '../ide_router';
export default { export default {
components: { components: {
...@@ -9,6 +10,10 @@ export default { ...@@ -9,6 +10,10 @@ export default {
EditorMode, EditorMode,
}, },
props: { props: {
activeFile: {
type: Object,
required: true,
},
files: { files: {
type: Array, type: Array,
required: true, required: true,
...@@ -38,7 +43,18 @@ export default { ...@@ -38,7 +43,18 @@ export default {
this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth; this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
}, },
methods: { methods: {
...mapActions(['updateViewer']), ...mapActions(['updateViewer', 'removePendingTab']),
openFileViewer(viewer) {
this.updateViewer(viewer);
if (this.activeFile.pending) {
return this.removePendingTab(this.activeFile).then(() => {
router.push(`/project${this.activeFile.url}`);
});
}
return null;
},
}, },
}; };
</script> </script>
...@@ -60,7 +76,7 @@ export default { ...@@ -60,7 +76,7 @@ export default {
:show-shadow="showShadow" :show-shadow="showShadow"
:has-changes="hasChanges" :has-changes="hasChanges"
:merge-request-id="mergeRequestId" :merge-request-id="mergeRequestId"
@click="updateViewer" @click="openFileViewer"
/> />
</div> </div>
</template> </template>
...@@ -77,7 +77,11 @@ router.beforeEach((to, from, next) => { ...@@ -77,7 +77,11 @@ router.beforeEach((to, from, next) => {
if (to.params[0]) { if (to.params[0]) {
const path = const path =
to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0]; to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0];
const treeEntry = store.state.entries[path]; const treeEntryKey = Object.keys(store.state.entries).find(
key => key === path && !store.state.entries[key].pending,
);
const treeEntry = store.state.entries[treeEntryKey];
if (treeEntry) { if (treeEntry) {
store.dispatch('handleTreeEntryAction', treeEntry); store.dispatch('handleTreeEntryAction', treeEntry);
} }
......
...@@ -13,12 +13,12 @@ export default class Model { ...@@ -13,12 +13,12 @@ export default class Model {
(this.originalModel = this.monaco.editor.createModel( (this.originalModel = this.monaco.editor.createModel(
this.file.raw, this.file.raw,
undefined, undefined,
new this.monaco.Uri(null, null, `original/${this.file.path}`), new this.monaco.Uri(null, null, `original/${this.file.key}`),
)), )),
(this.model = this.monaco.editor.createModel( (this.model = this.monaco.editor.createModel(
this.content, this.content,
undefined, undefined,
new this.monaco.Uri(null, null, this.file.path), new this.monaco.Uri(null, null, this.file.key),
)), )),
); );
if (this.file.mrChange) { if (this.file.mrChange) {
...@@ -36,7 +36,7 @@ export default class Model { ...@@ -36,7 +36,7 @@ export default class Model {
this.updateContent = this.updateContent.bind(this); this.updateContent = this.updateContent.bind(this);
this.dispose = this.dispose.bind(this); this.dispose = this.dispose.bind(this);
eventHub.$on(`editor.update.model.dispose.${this.file.path}`, this.dispose); eventHub.$on(`editor.update.model.dispose.${this.file.key}`, this.dispose);
eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent); eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent);
} }
...@@ -53,7 +53,7 @@ export default class Model { ...@@ -53,7 +53,7 @@ export default class Model {
} }
get path() { get path() {
return this.file.path; return this.file.key;
} }
getModel() { getModel() {
...@@ -91,7 +91,7 @@ export default class Model { ...@@ -91,7 +91,7 @@ export default class Model {
this.disposable.dispose(); this.disposable.dispose();
this.events.clear(); this.events.clear();
eventHub.$off(`editor.update.model.dispose.${this.file.path}`, this.dispose); eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose);
eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent); eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent);
} }
} }
...@@ -9,17 +9,17 @@ export default class ModelManager { ...@@ -9,17 +9,17 @@ export default class ModelManager {
this.models = new Map(); this.models = new Map();
} }
hasCachedModel(path) { hasCachedModel(key) {
return this.models.has(path); return this.models.has(key);
} }
getModel(path) { getModel(key) {
return this.models.get(path); return this.models.get(key);
} }
addModel(file) { addModel(file) {
if (this.hasCachedModel(file.path)) { if (this.hasCachedModel(file.key)) {
return this.getModel(file.path); return this.getModel(file.key);
} }
const model = new Model(this.monaco, file); const model = new Model(this.monaco, file);
...@@ -27,7 +27,7 @@ export default class ModelManager { ...@@ -27,7 +27,7 @@ export default class ModelManager {
this.disposable.add(model); this.disposable.add(model);
eventHub.$on( eventHub.$on(
`editor.update.model.dispose.${file.path}`, `editor.update.model.dispose.${file.key}`,
this.removeCachedModel.bind(this, file), this.removeCachedModel.bind(this, file),
); );
...@@ -35,12 +35,9 @@ export default class ModelManager { ...@@ -35,12 +35,9 @@ export default class ModelManager {
} }
removeCachedModel(file) { removeCachedModel(file) {
this.models.delete(file.path); this.models.delete(file.key);
eventHub.$off( eventHub.$off(`editor.update.model.dispose.${file.key}`, this.removeCachedModel);
`editor.update.model.dispose.${file.path}`,
this.removeCachedModel,
);
} }
dispose() { dispose() {
......
...@@ -22,7 +22,7 @@ export const discardAllChanges = ({ state, commit, dispatch }) => { ...@@ -22,7 +22,7 @@ export const discardAllChanges = ({ state, commit, dispatch }) => {
}; };
export const closeAllFiles = ({ state, dispatch }) => { export const closeAllFiles = ({ state, dispatch }) => {
state.openFiles.forEach(file => dispatch('closeFile', file.path)); state.openFiles.forEach(file => dispatch('closeFile', file));
}; };
export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
......
...@@ -6,24 +6,34 @@ import * as types from '../mutation_types'; ...@@ -6,24 +6,34 @@ import * as types from '../mutation_types';
import router from '../../ide_router'; import router from '../../ide_router';
import { setPageTitle } from '../utils'; import { setPageTitle } from '../utils';
export const closeFile = ({ commit, state, getters, dispatch }, path) => { export const closeFile = ({ commit, state, dispatch }, file) => {
const indexOfClosedFile = state.openFiles.findIndex(f => f.path === path); const path = file.path;
const file = state.entries[path]; const indexOfClosedFile = state.openFiles.findIndex(f => f.key === file.key);
const fileWasActive = file.active; const fileWasActive = file.active;
commit(types.TOGGLE_FILE_OPEN, path); if (file.pending) {
commit(types.SET_FILE_ACTIVE, { path, active: false }); commit(types.REMOVE_PENDING_TAB, file);
} else {
commit(types.TOGGLE_FILE_OPEN, path);
commit(types.SET_FILE_ACTIVE, { path, active: false });
}
if (state.openFiles.length > 0 && fileWasActive) { if (state.openFiles.length > 0 && fileWasActive) {
const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1; const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
const nextFileToOpen = state.entries[state.openFiles[nextIndexToOpen].path]; const nextFileToOpen = state.openFiles[nextIndexToOpen];
router.push(`/project${nextFileToOpen.url}`); if (nextFileToOpen.pending) {
dispatch('updateViewer', 'diff');
dispatch('openPendingTab', nextFileToOpen);
} else {
dispatch('updateDelayViewerUpdated', true);
router.push(`/project${nextFileToOpen.url}`);
}
} else if (!state.openFiles.length) { } else if (!state.openFiles.length) {
router.push(`/project/${file.projectId}/tree/${file.branchId}/`); router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
} }
eventHub.$emit(`editor.update.model.dispose.${file.path}`); eventHub.$emit(`editor.update.model.dispose.${file.key}`);
}; };
export const setFileActive = ({ commit, state, getters, dispatch }, path) => { export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
...@@ -162,3 +172,23 @@ export const stageChange = ({ commit }, path) => { ...@@ -162,3 +172,23 @@ export const stageChange = ({ commit }, path) => {
export const unstageChange = ({ commit }, path) => { export const unstageChange = ({ commit }, path) => {
commit(types.UNSTAGE_CHANGE, path); commit(types.UNSTAGE_CHANGE, path);
}; };
export const openPendingTab = ({ commit, getters, dispatch, state }, file) => {
if (getters.activeFile && getters.activeFile.path === file.path && state.viewer === 'diff') {
return false;
}
commit(types.ADD_PENDING_TAB, { file });
dispatch('scrollToTab');
router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
return true;
};
export const removePendingTab = ({ commit }, file) => {
commit(types.REMOVE_PENDING_TAB, file);
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
};
...@@ -55,3 +55,5 @@ export const STAGE_CHANGE = 'STAGE_CHANGE'; ...@@ -55,3 +55,5 @@ export const STAGE_CHANGE = 'STAGE_CHANGE';
export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE'; export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE';
export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
...@@ -5,6 +5,14 @@ export default { ...@@ -5,6 +5,14 @@ export default {
Object.assign(state.entries[path], { Object.assign(state.entries[path], {
active, active,
}); });
if (active && !state.entries[path].pending) {
Object.assign(state, {
openFiles: state.openFiles.map(f =>
Object.assign(f, { active: f.pending ? false : f.active }),
),
});
}
}, },
[types.TOGGLE_FILE_OPEN](state, path) { [types.TOGGLE_FILE_OPEN](state, path) {
Object.assign(state.entries[path], { Object.assign(state.entries[path], {
...@@ -12,10 +20,14 @@ export default { ...@@ -12,10 +20,14 @@ export default {
}); });
if (state.entries[path].opened) { if (state.entries[path].opened) {
state.openFiles.push(state.entries[path]); Object.assign(state, {
openFiles: state.openFiles.filter(f => f.path !== path).concat(state.entries[path]),
});
} else { } else {
const file = state.entries[path];
Object.assign(state, { Object.assign(state, {
openFiles: state.openFiles.filter(f => f.path !== path), openFiles: state.openFiles.filter(f => f.key !== file.key),
}); });
} }
}, },
...@@ -141,4 +153,37 @@ export default { ...@@ -141,4 +153,37 @@ export default {
changed, changed,
}); });
}, },
[types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) {
const pendingTab = state.openFiles.find(f => f.path === file.path && f.pending);
let openFiles = state.openFiles.map(f =>
Object.assign(f, { active: f.path === file.path, opened: false }),
);
if (!pendingTab) {
const openFile = openFiles.find(f => f.path === file.path);
openFiles = openFiles.concat(openFile ? null : file).reduce((acc, f) => {
if (!f) return acc;
if (f.path === file.path) {
return acc.concat({
...f,
active: true,
pending: true,
opened: true,
key: `${keyPrefix}-${f.key}`,
});
}
return acc.concat(f);
}, []);
}
Object.assign(state, { openFiles });
},
[types.REMOVE_PENDING_TAB](state, file) {
Object.assign(state, {
openFiles: state.openFiles.filter(f => f.key !== file.key),
});
},
}; };
export const dataStructure = () => ({ export const dataStructure = () => ({
id: '', id: '',
// Key will contain a mixture of ID and path
// it can also contain a prefix `pending-` for files opened in review mode
key: '', key: '',
type: '', type: '',
projectId: '', projectId: '',
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
return `#${this.job.runner.id}`; return `#${this.job.runner.id}`;
}, },
hasTimeout() { hasTimeout() {
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== ''; return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
}, },
timeout() { timeout() {
if (this.job.metadata == null) { if (this.job.metadata == null) {
......
...@@ -33,6 +33,7 @@ export const checkPageAndAction = (page, action) => { ...@@ -33,6 +33,7 @@ export const checkPageAndAction = (page, action) => {
export const isInIssuePage = () => checkPageAndAction('issues', 'show'); export const isInIssuePage = () => checkPageAndAction('issues', 'show');
export const isInMRPage = () => checkPageAndAction('merge_requests', 'show'); export const isInMRPage = () => checkPageAndAction('merge_requests', 'show');
export const isInEpicPage = () => checkPageAndAction('epics', 'show');
export const isInNoteablePage = () => isInIssuePage() || isInMRPage(); export const isInNoteablePage = () => isInIssuePage() || isInMRPage();
export const hasVueMRDiscussionsCookie = () => Cookies.get('vue_mr_discussions'); export const hasVueMRDiscussionsCookie = () => Cookies.get('vue_mr_discussions');
......
...@@ -99,6 +99,10 @@ export default { ...@@ -99,6 +99,10 @@ export default {
'js-note-target-reopen': !this.isOpen, 'js-note-target-reopen': !this.isOpen,
}; };
}, },
supportQuickActions() {
// Disable quick actions support for Epics
return this.noteableType !== constants.EPIC_NOTEABLE_TYPE;
},
markdownDocsPath() { markdownDocsPath() {
return this.getNotesData.markdownDocsPath; return this.getNotesData.markdownDocsPath;
}, },
...@@ -355,7 +359,7 @@ Please check your network connection and try again.`; ...@@ -355,7 +359,7 @@ Please check your network connection and try again.`;
name="note[note]" name="note[note]"
class="note-textarea js-vue-comment-form class="note-textarea js-vue-comment-form
js-gfm-input js-autosize markdown-area js-vue-textarea" js-gfm-input js-autosize markdown-area js-vue-textarea"
data-supports-quick-actions="true" :data-supports-quick-actions="supportQuickActions"
aria-label="Description" aria-label="Description"
v-model="note" v-model="note"
ref="textarea" ref="textarea"
......
...@@ -50,7 +50,11 @@ export default { ...@@ -50,7 +50,11 @@ export default {
...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']), ...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']),
noteableType() { noteableType() {
// FIXME -- @fatihacet Get this from JSON data. // FIXME -- @fatihacet Get this from JSON data.
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE } = constants; const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants;
if (this.noteableData.noteableType === EPIC_NOTEABLE_TYPE) {
return EPIC_NOTEABLE_TYPE;
}
return this.noteableData.merge_params return this.noteableData.merge_params
? MERGE_REQUEST_NOTEABLE_TYPE ? MERGE_REQUEST_NOTEABLE_TYPE
......
...@@ -10,6 +10,7 @@ export const CLOSED = 'closed'; ...@@ -10,6 +10,7 @@ export const CLOSED = 'closed';
export const EMOJI_THUMBSUP = 'thumbsup'; export const EMOJI_THUMBSUP = 'thumbsup';
export const EMOJI_THUMBSDOWN = 'thumbsdown'; export const EMOJI_THUMBSDOWN = 'thumbsdown';
export const ISSUE_NOTEABLE_TYPE = 'issue'; export const ISSUE_NOTEABLE_TYPE = 'issue';
export const EPIC_NOTEABLE_TYPE = 'epic';
export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request'; export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request';
export const UNRESOLVE_NOTE_METHOD_NAME = 'delete'; export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
export const RESOLVE_NOTE_METHOD_NAME = 'post'; export const RESOLVE_NOTE_METHOD_NAME = 'post';
...@@ -12,8 +12,11 @@ document.addEventListener( ...@@ -12,8 +12,11 @@ document.addEventListener(
data() { data() {
const notesDataset = document.getElementById('js-vue-notes').dataset; const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData); const parsedUserData = JSON.parse(notesDataset.currentUserData);
const noteableData = JSON.parse(notesDataset.noteableData);
let currentUserData = {}; let currentUserData = {};
noteableData.noteableType = notesDataset.noteableType;
if (parsedUserData) { if (parsedUserData) {
currentUserData = { currentUserData = {
id: parsedUserData.id, id: parsedUserData.id,
...@@ -25,7 +28,7 @@ document.addEventListener( ...@@ -25,7 +28,7 @@ document.addEventListener(
} }
return { return {
noteableData: JSON.parse(notesDataset.noteableData), noteableData,
currentUserData, currentUserData,
notesData: JSON.parse(notesDataset.notesData), notesData: JSON.parse(notesDataset.notesData),
}; };
......
...@@ -14,6 +14,8 @@ export default { ...@@ -14,6 +14,8 @@ export default {
return constants.MERGE_REQUEST_NOTEABLE_TYPE; return constants.MERGE_REQUEST_NOTEABLE_TYPE;
case 'Issue': case 'Issue':
return constants.ISSUE_NOTEABLE_TYPE; return constants.ISSUE_NOTEABLE_TYPE;
case 'Epic':
return constants.EPIC_NOTEABLE_TYPE;
default: default:
return ''; return '';
} }
......
...@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants'; ...@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({ initFilteredSearch({
page: FILTERED_SEARCH.ISSUES, page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
}); });
projectSelect(); projectSelect();
}); });
...@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants'; ...@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({ initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS, page: FILTERED_SEARCH.MERGE_REQUESTS,
isGroupDecendent: true,
}); });
projectSelect(); projectSelect();
}); });
...@@ -706,8 +706,8 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -706,8 +706,8 @@ button.mini-pipeline-graph-dropdown-toggle {
// dropdown content for big and mini pipeline // dropdown content for big and mini pipeline
.big-pipeline-graph-dropdown-menu, .big-pipeline-graph-dropdown-menu,
.mini-pipeline-graph-dropdown-menu { .mini-pipeline-graph-dropdown-menu {
width: 195px; width: 240px;
max-width: 195px; max-width: 240px;
.scrollable-menu { .scrollable-menu {
padding: 0; padding: 0;
...@@ -750,7 +750,7 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -750,7 +750,7 @@ button.mini-pipeline-graph-dropdown-toggle {
height: #{$ci-action-icon-size - 6}; height: #{$ci-action-icon-size - 6};
left: -3px; left: -3px;
position: relative; position: relative;
top: -2px; top: -1px;
&.icon-action-stop, &.icon-action-stop,
&.icon-action-cancel { &.icon-action-cancel {
...@@ -931,13 +931,11 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -931,13 +931,11 @@ button.mini-pipeline-graph-dropdown-toggle {
*/ */
&.dropdown-menu { &.dropdown-menu {
transform: translate(-80%, 0); transform: translate(-80%, 0);
min-width: 150px;
@media(min-width: $screen-md-min) { @media(min-width: $screen-md-min) {
transform: translate(-50%, 0); transform: translate(-50%, 0);
right: auto; right: auto;
left: 50%; left: 50%;
min-width: 240px;
} }
} }
} }
......
...@@ -5,7 +5,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -5,7 +5,7 @@ class Admin::GroupsController < Admin::ApplicationController
def index def index
@groups = Group.with_statistics.with_route @groups = Group.with_statistics.with_route
@groups = @groups.sort(@sort = params[:sort]) @groups = @groups.sort_by_attribute(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present? @groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page]) @groups = @groups.page(params[:page])
end end
......
...@@ -4,7 +4,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -4,7 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
def index def index
@users = User.order_name_asc.filter(params[:filter]) @users = User.order_name_asc.filter(params[:filter])
@users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present? @users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present?
@users = @users.sort(@sort = params[:sort]) @users = @users.sort_by_attribute(@sort = params[:sort])
@users = @users.page(params[:page]) @users = @users.page(params[:page])
end end
......
...@@ -229,10 +229,6 @@ class ApplicationController < ActionController::Base ...@@ -229,10 +229,6 @@ class ApplicationController < ActionController::Base
@event_filter ||= EventFilter.new(filters) @event_filter ||= EventFilter.new(filters)
end end
def gitlab_ldap_access(&block)
Gitlab::Auth::LDAP::Access.open { |access| yield(access) }
end
# JSON for infinite scroll via Pager object # JSON for infinite scroll via Pager object
def pager_json(partial, count, locals = {}) def pager_json(partial, count, locals = {})
html = render_to_string( html = render_to_string(
......
...@@ -14,7 +14,7 @@ module GroupTree ...@@ -14,7 +14,7 @@ module GroupTree
end end
@groups = @groups.with_selects_for_list(archived: params[:archived]) @groups = @groups.with_selects_for_list(archived: params[:archived])
.sort(@sort = params[:sort]) .sort_by_attribute(@sort = params[:sort])
.page(params[:page]) .page(params[:page])
respond_to do |format| respond_to do |format|
......
...@@ -88,11 +88,15 @@ module IssuableActions ...@@ -88,11 +88,15 @@ module IssuableActions
discussions = Discussion.build_collection(notes, issuable) discussions = Discussion.build_collection(notes, issuable)
render json: DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user).represent(discussions, context: self) render json: discussion_serializer.represent(discussions, context: self)
end end
private private
def discussion_serializer
DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user, note_entity: ProjectNoteEntity)
end
def recaptcha_check_if_spammable(should_redirect = true, &block) def recaptcha_check_if_spammable(should_redirect = true, &block)
return yield unless issuable.is_a? Spammable return yield unless issuable.is_a? Spammable
......
...@@ -212,7 +212,7 @@ module NotesActions ...@@ -212,7 +212,7 @@ module NotesActions
end end
def note_serializer def note_serializer
NoteSerializer.new(project: project, noteable: noteable, current_user: current_user) ProjectNoteSerializer.new(project: project, noteable: noteable, current_user: current_user)
end end
def note_project def note_project
......
...@@ -17,7 +17,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -17,7 +17,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = GroupMembersFinder.new(@group).execute @members = GroupMembersFinder.new(@group).execute
@members = @members.non_invite unless can?(current_user, :admin_group, @group) @members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.search(params[:search]) if params[:search].present? @members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort) @members = @members.sort_by_attribute(@sort)
@members = @members.page(params[:page]).per(50) @members = @members.page(params[:page]).per(50)
@members = present_members(@members.includes(:user)) @members = present_members(@members.includes(:user))
......
...@@ -51,7 +51,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -51,7 +51,7 @@ class ProfilesController < Profiles::ApplicationController
end end
def update_username def update_username
result = Users::UpdateService.new(current_user, user: @user, username: user_params[:username]).execute result = Users::UpdateService.new(current_user, user: @user, username: username_param).execute
options = if result[:status] == :success options = if result[:status] == :success
{ notice: "Username successfully changed" } { notice: "Username successfully changed" }
...@@ -72,6 +72,10 @@ class ProfilesController < Profiles::ApplicationController ...@@ -72,6 +72,10 @@ class ProfilesController < Profiles::ApplicationController
return render_404 unless @user.can_change_username? return render_404 unless @user.can_change_username?
end end
def username_param
@username_param ||= user_params.require(:username)
end
def user_params def user_params
@user_params ||= params.require(:user).permit( @user_params ||= params.require(:user).permit(
:avatar, :avatar,
......
...@@ -22,9 +22,13 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -22,9 +22,13 @@ class Projects::BranchesController < Projects::ApplicationController
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = repository.merged_branch_names(@branches.map(&:name)) @merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch) # n+1: https://gitlab.com/gitlab-org/gitaly/issues/992
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max Gitlab::GitalyClient.allow_n_plus_1_calls do
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end
end end
render render
......
...@@ -43,7 +43,7 @@ class Projects::DiscussionsController < Projects::ApplicationController ...@@ -43,7 +43,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
def render_json_with_discussions_serializer def render_json_with_discussions_serializer
render json: render json:
DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user) DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user, note_entity: ProjectNoteEntity)
.represent(discussion, context: self) .represent(discussion, context: self)
end end
......
...@@ -150,7 +150,8 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -150,7 +150,8 @@ class Projects::LabelsController < Projects::ApplicationController
end end
def find_labels def find_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute @available_labels ||=
LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups]).execute
end end
def authorize_admin_labels! def authorize_admin_labels!
......
...@@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def existing_oids def existing_oids
@existing_oids ||= begin @existing_oids ||= begin
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end end
end end
......
...@@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -14,7 +14,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def index def index
@sort = params[:sort] || 'due_date_asc' @sort = params[:sort] || 'due_date_asc'
@milestones = milestones.sort(@sort) @milestones = milestones.sort_by_attribute(@sort)
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -21,7 +21,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -21,7 +21,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end end
@project_members = present_members(@project_members.sort(@sort).page(params[:page])) @project_members = present_members(@project_members.sort_by_attribute(@sort).page(params[:page]))
@requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user)) @requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
@project_member = @project.project_members.new @project_member = @project.project_members.new
end end
......
...@@ -16,6 +16,10 @@ module Projects ...@@ -16,6 +16,10 @@ module Projects
@protected_tags = @project.protected_tags.order(:name).page(params[:page]) @protected_tags = @project.protected_tags.order(:name).page(params[:page])
@protected_branch = @project.protected_branches.new @protected_branch = @project.protected_branches.new
@protected_tag = @project.protected_tags.new @protected_tag = @project.protected_tags.new
@protected_branches_count = @protected_branches.reduce(0) { |sum, branch| sum + branch.matching(@project.repository.branches).size }
@protected_tags_count = @protected_tags.reduce(0) { |sum, tag| sum + tag.matching(@project.repository.tags).size }
load_gon_index load_gon_index
end end
......
...@@ -62,6 +62,6 @@ class Admin::ProjectsFinder ...@@ -62,6 +62,6 @@ class Admin::ProjectsFinder
def sort(items) def sort(items)
sort = params.fetch(:sort) { 'latest_activity_desc' } sort = params.fetch(:sort) { 'latest_activity_desc' }
items.sort(sort) items.sort_by_attribute(sort)
end end
end end
...@@ -337,7 +337,7 @@ class IssuableFinder ...@@ -337,7 +337,7 @@ class IssuableFinder
def sort(items) def sort(items)
# Ensure we always have an explicit sort order (instead of inheriting # Ensure we always have an explicit sort order (instead of inheriting
# multiple orders when combining ActiveRecord::Relation objects). # multiple orders when combining ActiveRecord::Relation objects).
params[:sort] ? items.sort(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc) params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
end end
def by_assignee(items) def by_assignee(items)
......
...@@ -28,9 +28,10 @@ class LabelsFinder < UnionFinder ...@@ -28,9 +28,10 @@ class LabelsFinder < UnionFinder
if project if project
if project.group.present? if project.group.present?
labels_table = Label.arel_table labels_table = Label.arel_table
group_ids = group_ids_for(project.group)
label_ids << Label.where( label_ids << Label.where(
labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].eq(project.group.id)).or( labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].in(group_ids)).or(
labels_table[:type].eq('ProjectLabel').and(labels_table[:project_id].eq(project.id)) labels_table[:type].eq('ProjectLabel').and(labels_table[:project_id].eq(project.id))
) )
) )
...@@ -38,11 +39,14 @@ class LabelsFinder < UnionFinder ...@@ -38,11 +39,14 @@ class LabelsFinder < UnionFinder
label_ids << project.labels label_ids << project.labels
end end
end end
elsif only_group_labels?
label_ids << Label.where(group_id: group_ids)
else else
if group?
group = Group.find(params[:group_id])
label_ids << Label.where(group_id: group_ids_for(group))
end
label_ids << Label.where(group_id: projects.group_ids) label_ids << Label.where(group_id: projects.group_ids)
label_ids << Label.where(project_id: projects.select(:id)) label_ids << Label.where(project_id: projects.select(:id)) unless only_group_labels?
end end
label_ids label_ids
...@@ -59,22 +63,33 @@ class LabelsFinder < UnionFinder ...@@ -59,22 +63,33 @@ class LabelsFinder < UnionFinder
items.where(title: title) items.where(title: title)
end end
def group_ids # Gets redacted array of group ids
# which can include the ancestors and descendants of the requested group.
def group_ids_for(group)
strong_memoize(:group_ids) do strong_memoize(:group_ids) do
groups_user_can_read_labels(groups_to_include).map(&:id) groups = groups_to_include(group)
groups_user_can_read_labels(groups).map(&:id)
end end
end end
def groups_to_include def groups_to_include(group)
group = Group.find(params[:group_id])
groups = [group] groups = [group]
groups += group.ancestors if params[:include_ancestor_groups].present? groups += group.ancestors if include_ancestor_groups?
groups += group.descendants if params[:include_descendant_groups].present? groups += group.descendants if include_descendant_groups?
groups groups
end end
def include_ancestor_groups?
params[:include_ancestor_groups]
end
def include_descendant_groups?
params[:include_descendant_groups]
end
def group? def group?
params[:group_id].present? params[:group_id].present?
end end
......
...@@ -124,7 +124,7 @@ class ProjectsFinder < UnionFinder ...@@ -124,7 +124,7 @@ class ProjectsFinder < UnionFinder
end end
def sort(items) def sort(items)
params[:sort].present? ? items.sort(params[:sort]) : items.order_id_desc params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
end end
def by_archived(projects) def by_archived(projects)
......
...@@ -119,7 +119,7 @@ class TodosFinder ...@@ -119,7 +119,7 @@ class TodosFinder
end end
def sort(items) def sort(items)
params[:sort] ? items.sort(params[:sort]) : items.order_id_desc params[:sort] ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
end end
def by_action(items) def by_action(items)
......
...@@ -53,10 +53,12 @@ module BoardsHelper ...@@ -53,10 +53,12 @@ module BoardsHelper
end end
def board_list_data def board_list_data
include_descendant_groups = @group&.present?
{ {
toggle: "dropdown", toggle: "dropdown",
list_labels_path: labels_filter_path(true), list_labels_path: labels_filter_path(true, include_ancestor_groups: true),
labels: labels_filter_path(true), labels: labels_filter_path(true, include_descendant_groups: include_descendant_groups),
labels_endpoint: @labels_endpoint, labels_endpoint: @labels_endpoint,
namespace_path: @namespace_path, namespace_path: @namespace_path,
project_path: @project&.path, project_path: @project&.path,
......
...@@ -129,13 +129,17 @@ module LabelsHelper ...@@ -129,13 +129,17 @@ module LabelsHelper
end end
end end
def labels_filter_path(only_group_labels = false) def labels_filter_path(only_group_labels = false, include_ancestor_groups: true, include_descendant_groups: false)
project = @target_project || @project project = @target_project || @project
options = {}
options[:include_ancestor_groups] = include_ancestor_groups if include_ancestor_groups
options[:include_descendant_groups] = include_descendant_groups if include_descendant_groups
if project if project
project_labels_path(project, :json) project_labels_path(project, :json, options)
elsif @group elsif @group
options = { only_group_labels: only_group_labels } if only_group_labels options[:only_group_labels] = only_group_labels if only_group_labels
group_labels_path(@group, :json, options) group_labels_path(@group, :json, options)
else else
dashboard_labels_path(:json) dashboard_labels_path(:json)
......
...@@ -151,16 +151,17 @@ module NotesHelper ...@@ -151,16 +151,17 @@ module NotesHelper
} }
end end
def notes_data(issuable) def discussions_path(issuable)
discussions_path = if issuable.is_a?(Issue)
if issuable.is_a?(Issue) discussions_project_issue_path(@project, issuable, format: :json)
discussions_project_issue_path(@project, issuable, format: :json) else
else discussions_project_merge_request_path(@project, issuable, format: :json)
discussions_project_merge_request_path(@project, issuable, format: :json) end
end end
def notes_data(issuable)
{ {
discussionsPath: discussions_path, discussionsPath: discussions_path(issuable),
registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'), registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'), newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'),
markdownDocsPath: help_page_path('user/markdown'), markdownDocsPath: help_page_path('user/markdown'),
...@@ -170,7 +171,6 @@ module NotesHelper ...@@ -170,7 +171,6 @@ module NotesHelper
notesPath: notes_url, notesPath: notes_url,
totalNotes: issuable.discussions.length, totalNotes: issuable.discussions.length,
lastFetchedAt: Time.now.to_i lastFetchedAt: Time.now.to_i
}.to_json }.to_json
end end
......
...@@ -123,7 +123,7 @@ module TreeHelper ...@@ -123,7 +123,7 @@ module TreeHelper
# returns the relative path of the first subdir that doesn't have only one directory descendant # returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(root_path, tree) def flatten_tree(root_path, tree)
return tree.flat_path.sub(%r{\A#{root_path}/}, '') if tree.flat_path.present? return tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '') if tree.flat_path.present?
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path) subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir? if subtree.count == 1 && subtree.first.dir?
......
...@@ -15,6 +15,7 @@ module Emails ...@@ -15,6 +15,7 @@ module Emails
setup_merge_request_mail(merge_request_id, recipient_id) setup_merge_request_mail(merge_request_id, recipient_id)
@new_commits = new_commits @new_commits = new_commits
@existing_commits = existing_commits @existing_commits = existing_commits
@updated_by_user = User.find(updated_by_user_id)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end end
......
...@@ -90,6 +90,7 @@ module Ci ...@@ -90,6 +90,7 @@ module Ci
before_save :ensure_token before_save :ensure_token
before_destroy { unscoped_project } before_destroy { unscoped_project }
before_create :ensure_metadata
after_create unless: :importing? do |build| after_create unless: :importing? do |build|
run_after_commit { BuildHooksWorker.perform_async(build.id) } run_after_commit { BuildHooksWorker.perform_async(build.id) }
end end
......
...@@ -32,7 +32,8 @@ class Commit ...@@ -32,7 +32,8 @@ class Commit
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field) def banzai_render_context(field)
context = { pipeline: :single_line, project: self.project } pipeline = field == :description ? :commit_description : :single_line
context = { pipeline: pipeline, project: self.project }
context[:author] = self.author if self.author context[:author] = self.author if self.author
context context
......
...@@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base
end end
def group_name def group_name
name.to_s.gsub(%r{\d+[\.\s:/\\]+\d+\s*}, '').strip name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
end end
def failed_but_allowed? def failed_but_allowed?
......
...@@ -137,7 +137,7 @@ module Issuable ...@@ -137,7 +137,7 @@ module Issuable
fuzzy_search(query, [:title, :description]) fuzzy_search(query, [:title, :description])
end end
def sort(method, excluded_labels: []) def sort_by_attribute(method, excluded_labels: [])
sorted = sorted =
case method.to_s case method.to_s
when 'downvotes_desc' then order_downvotes_desc when 'downvotes_desc' then order_downvotes_desc
......
...@@ -45,11 +45,11 @@ module Milestoneish ...@@ -45,11 +45,11 @@ module Milestoneish
end end
def sorted_issues(user) def sorted_issues(user)
issues_visible_to_user(user).preload_associations.sort('label_priority') issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority')
end end
def sorted_merge_requests def sorted_merge_requests
merge_requests.sort('label_priority') merge_requests.sort_by_attribute('label_priority')
end end
def upcoming? def upcoming?
......
...@@ -53,7 +53,7 @@ class Group < Namespace ...@@ -53,7 +53,7 @@ class Group < Namespace
Gitlab::Database.postgresql? Gitlab::Database.postgresql?
end end
def sort(method) def sort_by_attribute(method)
if method == 'storage_size_desc' if method == 'storage_size_desc'
# storage_size is a virtual column so we need to # storage_size is a virtual column so we need to
# pass a string to avoid AR adding the table name # pass a string to avoid AR adding the table name
......
...@@ -116,7 +116,7 @@ class Issue < ActiveRecord::Base ...@@ -116,7 +116,7 @@ class Issue < ActiveRecord::Base
'project_id' 'project_id'
end end
def self.sort(method, excluded_labels: []) def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s case method.to_s
when 'due_date' then order_due_date_asc when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc when 'due_date_asc' then order_due_date_asc
......
...@@ -96,7 +96,7 @@ class Member < ActiveRecord::Base ...@@ -96,7 +96,7 @@ class Member < ActiveRecord::Base
joins(:user).merge(User.search(query)) joins(:user).merge(User.search(query))
end end
def sort(method) def sort_by_attribute(method)
case method.to_s case method.to_s
when 'access_level_asc' then reorder(access_level: :asc) when 'access_level_asc' then reorder(access_level: :asc)
when 'access_level_desc' then reorder(access_level: :desc) when 'access_level_desc' then reorder(access_level: :desc)
......
...@@ -138,7 +138,7 @@ class Milestone < ActiveRecord::Base ...@@ -138,7 +138,7 @@ class Milestone < ActiveRecord::Base
User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).uniq User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).uniq
end end
def self.sort(method) def self.sort_by_attribute(method)
case method.to_s case method.to_s
when 'due_date_asc' when 'due_date_asc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
......
...@@ -379,12 +379,15 @@ class Note < ActiveRecord::Base ...@@ -379,12 +379,15 @@ class Note < ActiveRecord::Base
def expire_etag_cache def expire_etag_cache
return unless noteable&.discussions_rendered_on_frontend? return unless noteable&.discussions_rendered_on_frontend?
key = Gitlab::Routing.url_helpers.project_noteable_notes_path( Gitlab::EtagCaching::Store.new.touch(etag_key)
end
def etag_key
Gitlab::Routing.url_helpers.project_noteable_notes_path(
project, project,
target_type: noteable_type.underscore, target_type: noteable_type.underscore,
target_id: noteable_id target_id: noteable_id
) )
Gitlab::EtagCaching::Store.new.touch(key)
end end
def touch(*args) def touch(*args)
......
...@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base ...@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base
Gitlab::VisibilityLevel.options Gitlab::VisibilityLevel.options
end end
def sort(method) def sort_by_attribute(method)
case method.to_s case method.to_s
when 'storage_size_desc' when 'storage_size_desc'
# storage_size is a joined column so we need to # storage_size is a joined column so we need to
...@@ -566,9 +566,7 @@ class Project < ActiveRecord::Base ...@@ -566,9 +566,7 @@ class Project < ActiveRecord::Base
def add_import_job def add_import_job
job_id = job_id =
if forked? if forked?
RepositoryForkWorker.perform_async(id, RepositoryForkWorker.perform_async(id)
forked_from_project.repository_storage_path,
forked_from_project.disk_path)
elsif gitlab_project_import? elsif gitlab_project_import?
# Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-ce/issues/26189 is solved. # Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-ce/issues/26189 is solved.
RepositoryImportWorker.set(retry: false).perform_async(self.id) RepositoryImportWorker.set(retry: false).perform_async(self.id)
...@@ -1068,6 +1066,16 @@ class Project < ActiveRecord::Base ...@@ -1068,6 +1066,16 @@ class Project < ActiveRecord::Base
end end
end end
# This will return all `lfs_objects` that are accessible to the project.
# So this might be `self.lfs_objects` if the project is not part of a fork
# network, or it is the base of the fork network.
#
# TODO: refactor this to get the correct lfs objects when implementing
# https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
def all_lfs_objects
lfs_storage_project.lfs_objects
end
def personal? def personal?
!group !group
end end
......
...@@ -50,7 +50,7 @@ class Todo < ActiveRecord::Base ...@@ -50,7 +50,7 @@ class Todo < ActiveRecord::Base
# Priority sorting isn't displayed in the dropdown, because we don't show # Priority sorting isn't displayed in the dropdown, because we don't show
# milestones, but still show something if the user has a URL with that # milestones, but still show something if the user has a URL with that
# selected. # selected.
def sort(method) def sort_by_attribute(method)
sorted = sorted =
case method.to_s case method.to_s
when 'priority', 'label_priority' then order_by_labels_priority when 'priority', 'label_priority' then order_by_labels_priority
......
...@@ -256,7 +256,7 @@ class User < ActiveRecord::Base ...@@ -256,7 +256,7 @@ class User < ActiveRecord::Base
end end
end end
def sort(method) def sort_by_attribute(method)
order_method = method || 'id_desc' order_method = method || 'id_desc'
case order_method.to_s case order_method.to_s
......
class BuildMetadataEntity < Grape::Entity class BuildMetadataEntity < Grape::Entity
expose :timeout_human_readable do |metadata| expose :timeout_human_readable
metadata.timeout_human_readable unless metadata.timeout.nil?
end
expose :timeout_source do |metadata| expose :timeout_source do |metadata|
metadata.present.timeout_source metadata.present.timeout_source
end end
......
...@@ -4,7 +4,9 @@ class DiscussionEntity < Grape::Entity ...@@ -4,7 +4,9 @@ class DiscussionEntity < Grape::Entity
expose :id, :reply_id expose :id, :reply_id
expose :expanded?, as: :expanded expose :expanded?, as: :expanded
expose :notes, using: NoteEntity expose :notes do |discussion, opts|
request.note_entity.represent(discussion.notes, opts)
end
expose :individual_note?, as: :individual_note expose :individual_note?, as: :individual_note
expose :resolvable?, as: :resolvable expose :resolvable?, as: :resolvable
...@@ -12,7 +14,7 @@ class DiscussionEntity < Grape::Entity ...@@ -12,7 +14,7 @@ class DiscussionEntity < Grape::Entity
expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion| expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion|
resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id) resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id)
end end
expose :resolve_with_issue_path do |discussion| expose :resolve_with_issue_path, if: -> (d, _) { d.resolvable? } do |discussion|
new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id) new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id)
end end
......
...@@ -5,10 +5,6 @@ class NoteEntity < API::Entities::Note ...@@ -5,10 +5,6 @@ class NoteEntity < API::Entities::Note
expose :author, using: NoteUserEntity expose :author, using: NoteUserEntity
expose :human_access do |note|
note.project.team.human_max_access(note.author_id)
end
unexpose :note, as: :body unexpose :note, as: :body
expose :note expose :note
...@@ -37,36 +33,10 @@ class NoteEntity < API::Entities::Note ...@@ -37,36 +33,10 @@ class NoteEntity < API::Entities::Note
expose :emoji_awardable?, as: :emoji_awardable expose :emoji_awardable?, as: :emoji_awardable
expose :award_emoji, if: -> (note, _) { note.emoji_awardable? }, using: AwardEmojiEntity expose :award_emoji, if: -> (note, _) { note.emoji_awardable? }, using: AwardEmojiEntity
expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note|
if note.for_personal_snippet?
toggle_award_emoji_snippet_note_path(note.noteable, note)
else
toggle_award_emoji_project_note_path(note.project, note.id)
end
end
expose :report_abuse_path do |note| expose :report_abuse_path do |note|
new_abuse_report_path(user_id: note.author.id, ref_url: Gitlab::UrlBuilder.build(note)) new_abuse_report_path(user_id: note.author.id, ref_url: Gitlab::UrlBuilder.build(note))
end end
expose :path do |note|
if note.for_personal_snippet?
snippet_note_path(note.noteable, note)
else
project_note_path(note.project, note)
end
end
expose :resolve_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
resolve_project_merge_request_discussion_path(note.project, note.noteable, note.discussion_id)
end
expose :resolve_with_issue_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
new_project_issue_path(note.project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id)
end
expose :attachment, using: NoteAttachmentEntity, if: -> (note, _) { note.attachment? } expose :attachment, using: NoteAttachmentEntity, if: -> (note, _) { note.attachment? }
expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note|
delete_attachment_project_note_path(note.project, note)
end
end end
class NoteSerializer < BaseSerializer
entity NoteEntity
end
class ProjectNoteEntity < NoteEntity
expose :human_access do |note|
note.project.team.human_max_access(note.author_id)
end
expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note|
toggle_award_emoji_project_note_path(note.project, note.id)
end
expose :path do |note|
project_note_path(note.project, note)
end
expose :resolve_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
resolve_project_merge_request_discussion_path(note.project, note.noteable, note.discussion_id)
end
expose :resolve_with_issue_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note|
new_project_issue_path(note.project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id)
end
expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note|
delete_attachment_project_note_path(note.project, note)
end
end
class ProjectNoteSerializer < BaseSerializer
entity ProjectNoteEntity
end
...@@ -42,7 +42,10 @@ module Boards ...@@ -42,7 +42,10 @@ module Boards
) )
end end
attrs[:move_between_ids] = move_between_ids if move_between_ids if move_between_ids
attrs[:move_between_ids] = move_between_ids
attrs[:board_group_id] = board.group&.id
end
attrs attrs
end end
......
...@@ -12,11 +12,15 @@ module Boards ...@@ -12,11 +12,15 @@ module Boards
private private
def available_labels_for(board) def available_labels_for(board)
options = { include_ancestor_groups: true }
if board.group_board? if board.group_board?
parent.labels options.merge!(group_id: parent.id, only_group_labels: true)
else else
LabelsFinder.new(current_user, project_id: parent.id).execute options[:project_id] = parent.id
end end
LabelsFinder.new(current_user, options).execute
end end
def next_position(board) def next_position(board)
......
...@@ -106,7 +106,7 @@ class IssuableBaseService < BaseService ...@@ -106,7 +106,7 @@ class IssuableBaseService < BaseService
end end
def available_labels def available_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: true).execute
end end
def handle_quick_actions_on_create(issuable) def handle_quick_actions_on_create(issuable)
......
...@@ -55,9 +55,10 @@ module Issues ...@@ -55,9 +55,10 @@ module Issues
return unless params[:move_between_ids] return unless params[:move_between_ids]
after_id, before_id = params.delete(:move_between_ids) after_id, before_id = params.delete(:move_between_ids)
board_group_id = params.delete(:board_group_id)
issue_before = get_issue_if_allowed(issue.project, before_id) if before_id issue_before = get_issue_if_allowed(before_id, board_group_id)
issue_after = get_issue_if_allowed(issue.project, after_id) if after_id issue_after = get_issue_if_allowed(after_id, board_group_id)
issue.move_between(issue_before, issue_after) issue.move_between(issue_before, issue_after)
end end
...@@ -84,8 +85,16 @@ module Issues ...@@ -84,8 +85,16 @@ module Issues
private private
def get_issue_if_allowed(project, id) def get_issue_if_allowed(id, board_group_id = nil)
issue = project.issues.find(id) return unless id
issue =
if board_group_id
IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id)
else
project.issues.find(id)
end
issue if can?(current_user, :update_issue, issue) issue if can?(current_user, :update_issue, issue)
end end
......
...@@ -21,7 +21,8 @@ module Projects ...@@ -21,7 +21,8 @@ module Projects
end end
def labels(target = nil) def labels(target = nil)
labels = LabelsFinder.new(current_user, project_id: project.id).execute.select([:color, :title]) labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true)
.execute.select([:color, :title])
return labels unless target&.respond_to?(:labels) return labels unless target&.respond_to?(:labels)
......
...@@ -28,7 +28,7 @@ module Projects ...@@ -28,7 +28,7 @@ module Projects
end end
def save_services def save_services
[version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver].all?(&:save)
end end
def version_saver def version_saver
...@@ -55,6 +55,10 @@ module Projects ...@@ -55,6 +55,10 @@ module Projects
Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared) Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared)
end end
def lfs_saver
Gitlab::ImportExport::LfsSaver.new(project: project, shared: @shared)
end
def cleanup_and_notify_error def cleanup_and_notify_error
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}") Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
......
...@@ -61,7 +61,7 @@ module Projects ...@@ -61,7 +61,7 @@ module Projects
project.ensure_repository project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: refmap) project.repository.fetch_as_mirror(project.import_url, refmap: refmap)
else else
gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, project.import_url) gitlab_shell.import_repository(project.repository_storage, project.disk_path, project.import_url)
end end
rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e
# Expire cache to prevent scenarios such as: # Expire cache to prevent scenarios such as:
......
...@@ -31,15 +31,17 @@ module Projects ...@@ -31,15 +31,17 @@ module Projects
# Check if we did extract public directory # Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public') archive_public_path = File.join(archive_path, 'public')
raise FailedToExtractError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise InvaildStateError, 'pages are outdated' unless latest? raise InvaildStateError, 'pages are outdated' unless latest?
deploy_page!(archive_public_path) deploy_page!(archive_public_path)
success success
end end
rescue InvaildStateError, FailedToExtractError => e rescue InvaildStateError => e
register_failure
error(e.message) error(e.message)
rescue => e
error(e.message, false)
raise e
end end
private private
...@@ -50,12 +52,13 @@ module Projects ...@@ -50,12 +52,13 @@ module Projects
super super
end end
def error(message, http_status = nil) def error(message, allow_delete_artifact = true)
register_failure
log_error("Projects::UpdatePagesService: #{message}") log_error("Projects::UpdatePagesService: #{message}")
@status.allow_failure = !latest? @status.allow_failure = !latest?
@status.description = message @status.description = message
@status.drop(:script_failure) @status.drop(:script_failure)
delete_artifact! delete_artifact! if allow_delete_artifact
super super
end end
...@@ -76,7 +79,7 @@ module Projects ...@@ -76,7 +79,7 @@ module Projects
elsif artifacts.ends_with?('.zip') elsif artifacts.ends_with?('.zip')
extract_zip_archive!(temp_path) extract_zip_archive!(temp_path)
else else
raise FailedToExtractError, 'unsupported artifacts format' raise InvaildStateError, 'unsupported artifacts format'
end end
end end
...@@ -91,13 +94,13 @@ module Projects ...@@ -91,13 +94,13 @@ module Projects
end end
def extract_zip_archive!(temp_path) def extract_zip_archive!(temp_path)
raise FailedToExtractError, 'missing artifacts metadata' unless build.artifacts_metadata? raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata?
# Calculate page size after extract # Calculate page size after extract
public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true) public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
if public_entry.total_size > max_size if public_entry.total_size > max_size
raise FailedToExtractError, "artifacts for pages are too large: #{public_entry.total_size}" raise InvaildStateError, "artifacts for pages are too large: #{public_entry.total_size}"
end end
# Requires UnZip at least 6.00 Info-ZIP. # Requires UnZip at least 6.00 Info-ZIP.
......
...@@ -200,7 +200,7 @@ module QuickActions ...@@ -200,7 +200,7 @@ module QuickActions
end end
params '~label1 ~"label 2"' params '~label1 ~"label 2"'
condition do condition do
available_labels = LabelsFinder.new(current_user, project_id: project.id).execute available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute
current_user.can?(:"admin_#{issuable.to_ability_name}", project) && current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
available_labels.any? available_labels.any?
...@@ -562,7 +562,7 @@ module QuickActions ...@@ -562,7 +562,7 @@ module QuickActions
def find_labels(labels_param) def find_labels(labels_param)
extract_references(labels_param, :label) | extract_references(labels_param, :label) |
LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split, include_ancestor_groups: true).execute
end end
def find_label_references(labels_param) def find_label_references(labels_param)
...@@ -593,6 +593,7 @@ module QuickActions ...@@ -593,6 +593,7 @@ module QuickActions
def extract_references(arg, type) def extract_references(arg, type)
ext = Gitlab::ReferenceExtractor.new(project, current_user) ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze(arg, author: current_user) ext.analyze(arg, author: current_user)
ext.references(type) ext.references(type)
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :admin_notification_email, class: 'form-control'
.help-block
Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :email_author_in_body do
= f.check_box :email_author_in_body
Include author name in notification email body
.help-block
Some email servers do not support overriding the email sender name.
Enable this option to include the name of the author of the issue,
merge request or comment in the email body instead.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :html_emails_enabled do
= f.check_box :html_emails_enabled
Enable HTML emails
.help-block
By default GitLab sends emails in HTML and plain text formats so mail
clients can choose what format to use. Disable this option if you only
want to send emails in plain text format.
= f.submit 'Save changes', class: "btn btn-success"
This diff is collapsed.
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :gitaly_timeout_default, class: 'form-control'
.help-block
Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced
for git fetch/push operations or Sidekiq jobs.
.form-group
= f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :gitaly_timeout_fast, class: 'form-control'
.help-block
Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast.
If they exceed this threshold, there may be a problem with a storage shard and 'failing fast'
can help maintain the stability of the GitLab instance.
.form-group
= f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :gitaly_timeout_medium, class: 'form-control'
.help-block
Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :throttle_unauthenticated_enabled do
= f.check_box :throttle_unauthenticated_enabled
Enable unauthenticated request rate limit
%span.help-block
Helps reduce request volume (e.g. from crawlers or abusive bots)
.form-group
= f.label :throttle_unauthenticated_requests_per_period, 'Max requests per period per IP', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_unauthenticated_requests_per_period, class: 'form-control'
.form-group
= f.label :throttle_unauthenticated_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :throttle_authenticated_api_enabled do
= f.check_box :throttle_authenticated_api_enabled
Enable authenticated API request rate limit
%span.help-block
Helps reduce request volume (e.g. from crawlers or abusive bots)
.form-group
= f.label :throttle_authenticated_api_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_api_requests_per_period, class: 'form-control'
.form-group
= f.label :throttle_authenticated_api_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :throttle_authenticated_web_enabled do
= f.check_box :throttle_authenticated_web_enabled
Enable authenticated web request rate limit
%span.help-block
Helps reduce request volume (e.g. from crawlers or abusive bots)
.form-group
= f.label :throttle_authenticated_web_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_web_requests_per_period, class: 'form-control'
.form-group
= f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :koding_enabled do
= f.check_box :koding_enabled
Enable Koding
.help-block
Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
.form-group
= f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
.help-block
Koding has integration enabled out of the box for the
%strong gitlab
team, and you need to provide that team's URL here. Learn more in the
= succeed "." do
= link_to "Koding administration documentation", help_page_path("administration/integration/koding")
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :sentry_enabled do
= f.check_box :sentry_enabled
Enable Sentry
.help-block
%p This setting requires a restart to take effect.
Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here:
%a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com
.form-group
= f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :sentry_dsn, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :clientside_sentry_enabled do
= f.check_box :clientside_sentry_enabled
Enable Clientside Sentry
.help-block
Sentry can also be used for reporting and logging clientside exceptions.
%a{ href: 'https://sentry.io/for/javascript/', target: '_blank', rel: 'noopener noreferrer' } https://sentry.io/for/javascript/
.form-group
= f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :clientside_sentry_dsn, class: 'form-control'
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :allow_local_requests_from_hooks_and_services do
= f.check_box :allow_local_requests_from_hooks_and_services
Allow requests to the local network from hooks and services
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :authorized_keys_enabled do
= f.check_box :authorized_keys_enabled
Write to "authorized_keys" file
.help-block
By default, we write to the "authorized_keys" file to support Git
over SSH without additional configuration. GitLab can be optimized
to authenticate SSH keys via the database file. Only uncheck this
if you have configured your OpenSSH server to use the
AuthorizedKeysCommand. Click on the help icon for more details.
= link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :plantuml_enabled do
= f.check_box :plantuml_enabled
Enable PlantUML
.form-group
= f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
.help-block
Allow rendering of
= link_to "PlantUML", "http://plantuml.com"
diagrams in Asciidoc documents using an external PlantUML service.
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :polling_interval_multiplier, class: 'form-control'
.help-block
Change this value to influence how frequently the GitLab UI polls for updates.
If you set the value to 2 all polling intervals are multiplied
by 2, which means that polling happens half as frequently.
The multiplier can also have a decimal value.
The default value (1) is a reasonable choice for the majority of GitLab
installations. Set to 0 to completely disable polling.
= link_to icon('question-circle'), help_page_path('administration/polling')
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :container_registry_token_expire_delay, class: 'form-control'
= f.submit 'Save changes', class: "btn btn-success"
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.sub-section
%h4 Repository checks
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :repository_checks_enabled do
= f.check_box :repository_checks_enabled
Enable Repository Checks
.help-block
GitLab will periodically run
%a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
in all project and wiki repositories to look for silent disk corruption issues.
.form-group
.col-sm-offset-2.col-sm-10
= link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
.help-block
If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
.sub-section
%h4 Housekeeping
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :housekeeping_enabled do
= f.check_box :housekeeping_enabled
Enable automatic repository housekeeping (git repack, git gc)
.help-block
If you keep automatic housekeeping disabled for a long time Git
repository access on your GitLab server will become slower and your
repositories will use more disk space. We recommend to always leave
this enabled.
.checkbox
= f.label :housekeeping_bitmaps_enabled do
= f.check_box :housekeeping_bitmaps_enabled
Enable Git pack file bitmap creation
.help-block
Creating pack file bitmaps makes housekeeping take a little longer but
bitmaps should accelerate 'git clone' performance.
.form-group
= f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :housekeeping_incremental_repack_period, class: 'form-control'
.help-block
Number of Git pushes after which an incremental 'git repack' is run.
.form-group
= f.label :housekeeping_full_repack_period, 'Full repack period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :housekeeping_full_repack_period, class: 'form-control'
.help-block
Number of Git pushes after which a full 'git repack' is run.
.form-group
= f.label :housekeeping_gc_period, 'Git GC period', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :housekeeping_gc_period, class: 'form-control'
.help-block
Number of Git pushes after which 'git gc' is run.
= f.submit 'Save changes', class: "btn btn-success"
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