Commit e5398fc5 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'master' into renovate/gitlab-package

parents bb4b8905 97991630
...@@ -17,7 +17,7 @@ stages: ...@@ -17,7 +17,7 @@ stages:
# in cases where jobs require Docker-in-Docker, the job # in cases where jobs require Docker-in-Docker, the job
# definition must be extended with `.use-docker-in-docker` # definition must be extended with `.use-docker-in-docker`
default: default:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
tags: tags:
- gitlab-org - gitlab-org
# All jobs are interruptible by default # All jobs are interruptible by default
......
...@@ -272,3 +272,6 @@ Dangerfile @gl-quality/eng-prod ...@@ -272,3 +272,6 @@ Dangerfile @gl-quality/eng-prod
/lib/gitlab/experimentation/ @gitlab-org/growth/experiment-devs /lib/gitlab/experimentation/ @gitlab-org/growth/experiment-devs
/lib/gitlab/experimentation.rb @gitlab-org/growth/experiment-devs /lib/gitlab/experimentation.rb @gitlab-org/growth/experiment-devs
/lib/gitlab/experimentation_logger.rb @gitlab-org/growth/experiment-devs /lib/gitlab/experimentation_logger.rb @gitlab-org/growth/experiment-devs
[Legal]
/config/dependency_decisions.yml @gitlab-org/legal-reviewers
cloud-native-image: cloud-native-image:
extends: .cng:rules extends: .cng:rules
image: ruby:2.7-alpine image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
dependencies: [] dependencies: []
stage: post-test stage: post-test
variables: variables:
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
extends: extends:
- .default-retry - .default-retry
- .docs:rules:review-docs - .docs:rules:review-docs
image: ruby:2.7-alpine image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: review stage: review
needs: [] needs: []
variables: variables:
......
...@@ -259,13 +259,13 @@ coverage-frontend: ...@@ -259,13 +259,13 @@ coverage-frontend:
qa-frontend-node:10: qa-frontend-node:10:
extends: .qa-frontend-node extends: .qa-frontend-node
image: node:dubnium image: ${GITLAB_DEPENDENCY_PROXY}node:dubnium
qa-frontend-node:latest: qa-frontend-node:latest:
extends: extends:
- .qa-frontend-node - .qa-frontend-node
- .frontend:rules:qa-frontend-node-latest - .frontend:rules:qa-frontend-node-latest
image: node:latest image: ${GITLAB_DEPENDENCY_PROXY}node:latest
webpack-dev-server: webpack-dev-server:
extends: extends:
......
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
policy: pull policy: pull
.use-pg11: .use-pg11:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
services: services:
- name: postgres:11.6 - name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12: .use-pg12:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
services: services:
- name: postgres:12 - name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee: .use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
services: services:
- name: postgres:11.6 - name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12-ee: .use-pg12-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2.patched-golang-1.14-git-2.29-lfs-2.9-chrome-87-node-14.15-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
services: services:
- name: postgres:12 - name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
FOSS_ONLY: '1' FOSS_ONLY: '1'
.use-docker-in-docker: .use-docker-in-docker:
image: docker:${DOCKER_VERSION} image: ${GITLAB_DEPENDENCY_PROXY}docker:${DOCKER_VERSION}
services: services:
- docker:${DOCKER_VERSION}-dind - docker:${DOCKER_VERSION}-dind
variables: variables:
......
.notify-slack: .notify-slack:
image: alpine image: ${GITLAB_DEPENDENCY_PROXY}alpine
stage: notify stage: notify
dependencies: [] dependencies: []
cache: {} cache: {}
......
...@@ -47,7 +47,7 @@ update-qa-cache: ...@@ -47,7 +47,7 @@ update-qa-cache:
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up. policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
.package-and-qa-base: .package-and-qa-base:
image: ruby:2.7-alpine image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: qa stage: qa
retry: 0 retry: 0
script: script:
......
...@@ -502,7 +502,6 @@ rspec:feature-flags: ...@@ -502,7 +502,6 @@ rspec:feature-flags:
- .coverage-base - .coverage-base
- .rails:rules:rspec-feature-flags - .rails:rules:rspec-feature-flags
stage: post-test stage: post-test
allow_failure: true
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized) # We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
# so we use `dependencies` here. # so we use `dependencies` here.
dependencies: dependencies:
...@@ -522,7 +521,11 @@ rspec:feature-flags: ...@@ -522,7 +521,11 @@ rspec:feature-flags:
- memory-on-boot - memory-on-boot
script: script:
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519" - run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519"
- 'run_timed_command "bundle exec scripts/used-feature-flags" || (scripts/slack master-broken "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" ci_failing "GitLab Bot" && exit 1)' - if [ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]; then
run_timed_command "bundle exec scripts/used-feature-flags" || (scripts/slack master-broken "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" ci_failing "GitLab Bot" && exit 1);
else
run_timed_command "bundle exec scripts/used-feature-flags";
fi
# EE/FOSS: default refs (MRs, master, schedules) jobs # # EE/FOSS: default refs (MRs, master, schedules) jobs #
####################################################### #######################################################
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.merge-train-sync: .merge-train-sync:
# We don't need/want any global before/after commands, so we overwrite these # We don't need/want any global before/after commands, so we overwrite these
# settings. # settings.
image: alpine:edge image: ${GITLAB_DEPENDENCY_PROXY}alpine:edge
stage: sync stage: sync
before_script: before_script:
- apk add --no-cache --update curl bash jq - apk add --no-cache --update curl bash jq
......
...@@ -25,7 +25,7 @@ review-build-cng: ...@@ -25,7 +25,7 @@ review-build-cng:
extends: extends:
- .default-retry - .default-retry
- .review:rules:review-build-cng - .review:rules:review-build-cng
image: ruby:2.7-alpine image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: review-prepare stage: review-prepare
before_script: before_script:
- source ./scripts/utils.sh - source ./scripts/utils.sh
...@@ -199,7 +199,7 @@ review-performance: ...@@ -199,7 +199,7 @@ review-performance:
parallel-spec-reports: parallel-spec-reports:
extends: extends:
- .review:rules:mr-only-manual - .review:rules:mr-only-manual
image: ruby:2.7-alpine image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: post-qa stage: post-qa
dependencies: ["review-qa-all"] dependencies: ["review-qa-all"]
variables: variables:
......
...@@ -879,6 +879,7 @@ ...@@ -879,6 +879,7 @@
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-master-schedule-2-hourly - <<: *if-master-schedule-2-hourly
allow_failure: true
- <<: *if-merge-request-title-run-all-rspec - <<: *if-merge-request-title-run-all-rspec
.rails:rules:master-schedule-nightly--code-backstage: .rails:rules:master-schedule-nightly--code-backstage:
......
...@@ -26,7 +26,7 @@ cache gems: ...@@ -26,7 +26,7 @@ cache gems:
dont-interrupt-me: dont-interrupt-me:
extends: .setup:rules:dont-interrupt-me extends: .setup:rules:dont-interrupt-me
stage: sync stage: sync
image: alpine:edge image: ${GITLAB_DEPENDENCY_PROXY}alpine:edge
interruptible: false interruptible: false
variables: variables:
GIT_STRATEGY: none GIT_STRATEGY: none
...@@ -52,7 +52,7 @@ no_ee_check: ...@@ -52,7 +52,7 @@ no_ee_check:
verify-tests-yml: verify-tests-yml:
extends: extends:
- .setup:rules:verify-tests-yml - .setup:rules:verify-tests-yml
image: ruby:2.7-alpine image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: test stage: test
needs: [] needs: []
script: script:
...@@ -61,7 +61,7 @@ verify-tests-yml: ...@@ -61,7 +61,7 @@ verify-tests-yml:
- scripts/verify-tff-mapping - scripts/verify-tff-mapping
.detect-test-base: .detect-test-base:
image: ruby:2.7 image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7
needs: [] needs: []
stage: prepare stage: prepare
script: script:
......
.tests-metadata-state: .tests-metadata-state:
image: ruby:2.7 image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7
before_script: before_script:
- source scripts/utils.sh - source scripts/utils.sh
artifacts: artifacts:
......
workhorse: workhorse:
extends: .workhorse:rules:workhorse extends: .workhorse:rules:workhorse
image: golang:1.14 image: ${GITLAB_DEPENDENCY_PROXY}golang:1.14
stage: test stage: test
needs: [] needs: []
script: script:
......
...@@ -111,5 +111,5 @@ Use the following resources to find the appropriate labels: ...@@ -111,5 +111,5 @@ Use the following resources to find the appropriate labels:
- https://about.gitlab.com/handbook/product/categories/features/ - https://about.gitlab.com/handbook/product/categories/features/
--> -->
/label ~devops:: ~group: ~Category: /label ~devops:: ~group: ~Category:
/label ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"
/label ~feature /label ~feature
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
/label ~"feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Starter"/~"GitLab Premium"/~"GitLab Ultimate" /label ~"feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"
<!--- Use the following resources to find the appropriate labels: <!--- Use the following resources to find the appropriate labels:
......
...@@ -2647,113 +2647,6 @@ Style/FrozenStringLiteralComment: ...@@ -2647,113 +2647,6 @@ Style/FrozenStringLiteralComment:
- 'app/views/projects/tags/index.atom.builder' - 'app/views/projects/tags/index.atom.builder'
- 'app/views/users/show.atom.builder' - 'app/views/users/show.atom.builder'
- 'bin/secpick' - 'bin/secpick'
- 'config.ru'
- 'config/boot.rb'
- 'config/environment.rb'
- 'config/environments/development.rb'
- 'config/environments/production.rb'
- 'config/environments/test.rb'
- 'config/initializers/01_secret_token.rb'
- 'config/initializers/0_acts_as_taggable.rb'
- 'config/initializers/0_inject_feature_flags.rb'
- 'config/initializers/0_post_deployment_migrations.rb'
- 'config/initializers/1_settings.rb'
- 'config/initializers/2_gitlab.rb'
- 'config/initializers/5_backend.rb'
- 'config/initializers/6_validations.rb'
- 'config/initializers/7_prometheus_metrics.rb'
- 'config/initializers/7_redis.rb'
- 'config/initializers/8_devise.rb'
- 'config/initializers/8_gitaly.rb'
- 'config/initializers/9_fast_gettext.rb'
- 'config/initializers/action_dispatch_http_mime_negotiation.rb'
- 'config/initializers/action_mailer_hooks.rb'
- 'config/initializers/active_record_data_types.rb'
- 'config/initializers/active_record_ping.rb'
- 'config/initializers/active_record_preloader.rb'
- 'config/initializers/active_record_schema_ignore_tables.rb'
- 'config/initializers/active_record_table_definition.rb'
- 'config/initializers/ar_speed_up_migration_checking.rb'
- 'config/initializers/asset_proxy_settings.rb'
- 'config/initializers/attr_encrypted_no_db_connection.rb'
- 'config/initializers/backtrace_silencers.rb'
- 'config/initializers/batch_loader.rb'
- 'config/initializers/bootstrap_form.rb'
- 'config/initializers/bullet.rb'
- 'config/initializers/cluster_events_before_phased_restart.rb'
- 'config/initializers/console_message.rb'
- 'config/initializers/cookies_serializer.rb'
- 'config/initializers/date_time_formats.rb'
- 'config/initializers/default_url_options.rb'
- 'config/initializers/deprecations.rb'
- 'config/initializers/direct_upload_support.rb'
- 'config/initializers/doorkeeper.rb'
- 'config/initializers/doorkeeper_openid_connect.rb'
- 'config/initializers/etag_caching.rb'
- 'config/initializers/fill_shards.rb'
- 'config/initializers/fix_local_cache_middleware.rb'
- 'config/initializers/fog_google_https_private_urls.rb'
- 'config/initializers/forbid_sidekiq_in_transactions.rb'
- 'config/initializers/gettext_rails_i18n_patch.rb'
- 'config/initializers/gitlab_kas_secret.rb'
- 'config/initializers/gitlab_shell_secret_token.rb'
- 'config/initializers/gitlab_workhorse_secret.rb'
- 'config/initializers/go_get.rb'
- 'config/initializers/grpc.rb'
- 'config/initializers/hamlit.rb'
- 'config/initializers/health_check.rb'
- 'config/initializers/http_hostname_override.rb'
- 'config/initializers/kaminari_active_record_relation_methods_with_limit.rb'
- 'config/initializers/kaminari_config.rb'
- 'config/initializers/lograge.rb'
- 'config/initializers/mail_encoding_patch.rb'
- 'config/initializers/mime_types.rb'
- 'config/initializers/mini_magick.rb'
- 'config/initializers/new_framework_defaults.rb'
- 'config/initializers/octokit.rb'
- 'config/initializers/omniauth.rb'
- 'config/initializers/peek.rb'
- 'config/initializers/postgresql_cte.rb'
- 'config/initializers/premailer.rb'
- 'config/initializers/query_limiting.rb'
- 'config/initializers/rack_lineprof.rb'
- 'config/initializers/relative_naming_ci_namespace.rb'
- 'config/initializers/request_context.rb'
- 'config/initializers/request_profiler.rb'
- 'config/initializers/routing_draw.rb'
- 'config/initializers/sentry.rb'
- 'config/initializers/server_uptime.rb'
- 'config/initializers/session_store.rb'
- 'config/initializers/sherlock.rb'
- 'config/initializers/sprockets.rb'
- 'config/initializers/static_files.rb'
- 'config/initializers/time_zone.rb'
- 'config/initializers/trusted_proxies.rb'
- 'config/initializers/warden.rb'
- 'config/initializers/workhorse_multipart.rb'
- 'config/initializers/wrap_parameters.rb'
- 'config/initializers/zz_metrics.rb'
- 'config/initializers_before_autoloader/000_inflections.rb'
- 'config/object_store_settings.rb'
- 'config/routes.rb'
- 'config/routes/admin.rb'
- 'config/routes/api.rb'
- 'config/routes/dashboard.rb'
- 'config/routes/development.rb'
- 'config/routes/explore.rb'
- 'config/routes/git_http.rb'
- 'config/routes/google_api.rb'
- 'config/routes/help.rb'
- 'config/routes/import.rb'
- 'config/routes/legacy_builds.rb'
- 'config/routes/repository.rb'
- 'config/routes/sherlock.rb'
- 'config/routes/sidekiq.rb'
- 'config/routes/snippets.rb'
- 'config/routes/uploads.rb'
- 'config/routes/wiki.rb'
- 'config/smime_signature_settings.rb'
- 'config/spring.rb'
- 'danger/changes_size/Dangerfile' - 'danger/changes_size/Dangerfile'
- 'danger/metadata/Dangerfile' - 'danger/metadata/Dangerfile'
- 'db/migrate/20190325080727_truncate_user_fullname.rb' - 'db/migrate/20190325080727_truncate_user_fullname.rb'
......
9c2da9436f6a41a244a30deef6f48798f877e909 2c7c204731f6e4f1c8cdb3d8a705caf7acf6689d
...@@ -15,7 +15,7 @@ gem 'default_value_for', '~> 3.4.0' ...@@ -15,7 +15,7 @@ gem 'default_value_for', '~> 3.4.0'
# Supported DBs # Supported DBs
gem 'pg', '~> 1.1' gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28' gem 'rugged', '~> 1.0.1'
gem 'grape-path-helpers', '~> 1.6.1' gem 'grape-path-helpers', '~> 1.6.1'
gem 'faraday', '~> 1.0' gem 'faraday', '~> 1.0'
...@@ -266,7 +266,7 @@ gem 'babosa', '~> 1.0.2' ...@@ -266,7 +266,7 @@ gem 'babosa', '~> 1.0.2'
gem 'loofah', '~> 2.2' gem 'loofah', '~> 2.2'
# Working with license # Working with license
gem 'licensee', '~> 8.9' gem 'licensee', '~> 9.14.1'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.7' gem 'charlock_holmes', '~> 0.7.7'
...@@ -350,7 +350,7 @@ end ...@@ -350,7 +350,7 @@ end
group :development, :test do group :development, :test do
gem 'deprecation_toolkit', '~> 1.5.1', require: false gem 'deprecation_toolkit', '~> 1.5.1', require: false
gem 'bullet', '~> 6.1.0' gem 'bullet', '~> 6.1.3'
gem 'gitlab-pry-byebug', platform: :mri, require: ['pry-byebug', 'pry-byebug/pry_remote_ext'] gem 'gitlab-pry-byebug', platform: :mri, require: ['pry-byebug', 'pry-byebug/pry_remote_ext']
gem 'pry-rails', '~> 0.3.9' gem 'pry-rails', '~> 0.3.9'
gem 'pry-remote' gem 'pry-remote'
......
...@@ -147,7 +147,7 @@ GEM ...@@ -147,7 +147,7 @@ GEM
brakeman (4.2.1) brakeman (4.2.1)
browser (4.2.0) browser (4.2.0)
builder (3.2.4) builder (3.2.4)
bullet (6.1.0) bullet (6.1.3)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.11) uniform_notifier (~> 1.11)
bundler-audit (0.7.0.1) bundler-audit (0.7.0.1)
...@@ -267,6 +267,7 @@ GEM ...@@ -267,6 +267,7 @@ GEM
doorkeeper-openid_connect (1.7.5) doorkeeper-openid_connect (1.7.5)
doorkeeper (>= 5.2, < 5.5) doorkeeper (>= 5.2, < 5.5)
json-jwt (>= 1.11.0) json-jwt (>= 1.11.0)
dotenv (2.7.6)
dry-configurable (0.12.0) dry-configurable (0.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
dry-core (~> 0.5, >= 0.5.0) dry-core (~> 0.5, >= 0.5.0)
...@@ -674,8 +675,12 @@ GEM ...@@ -674,8 +675,12 @@ GEM
toml (= 0.2.0) toml (= 0.2.0)
with_env (= 1.1.0) with_env (= 1.1.0)
xml-simple xml-simple
licensee (8.9.2) licensee (9.14.1)
rugged (~> 0.24) dotenv (~> 2.0)
octokit (~> 4.17)
reverse_markdown (~> 1.0)
rugged (>= 0.24, < 2.0)
thor (>= 0.19, < 2.0)
listen (3.2.1) listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
...@@ -756,7 +761,7 @@ GEM ...@@ -756,7 +761,7 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.15.0) octokit (4.20.0)
faraday (>= 0.9) faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (3.10.6) oj (3.10.6)
...@@ -990,6 +995,8 @@ GEM ...@@ -990,6 +995,8 @@ GEM
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
retriable (3.1.2) retriable (3.1.2)
reverse_markdown (1.4.0)
nokogiri
rexml (3.2.4) rexml (3.2.4)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
...@@ -1072,7 +1079,7 @@ GEM ...@@ -1072,7 +1079,7 @@ GEM
rubyntlm (0.6.2) rubyntlm (0.6.2)
rubypants (0.2.0) rubypants (0.2.0)
rubyzip (2.0.0) rubyzip (2.0.0)
rugged (0.28.4.1) rugged (1.0.1)
safe_yaml (1.0.4) safe_yaml (1.0.4)
safety_net_attestation (0.4.0) safety_net_attestation (0.4.0)
jwt (~> 2.0) jwt (~> 2.0)
...@@ -1305,7 +1312,7 @@ DEPENDENCIES ...@@ -1305,7 +1312,7 @@ DEPENDENCIES
bootstrap_form (~> 4.2.0) bootstrap_form (~> 4.2.0)
brakeman (~> 4.2) brakeman (~> 4.2)
browser (~> 4.2) browser (~> 4.2)
bullet (~> 6.1.0) bullet (~> 6.1.3)
bundler-audit (~> 0.7.0.1) bundler-audit (~> 0.7.0.1)
capybara (~> 3.34.0) capybara (~> 3.34.0)
capybara-screenshot (~> 1.0.22) capybara-screenshot (~> 1.0.22)
...@@ -1417,7 +1424,7 @@ DEPENDENCIES ...@@ -1417,7 +1424,7 @@ DEPENDENCIES
lefthook (~> 0.7) lefthook (~> 0.7)
letter_opener_web (~> 1.3.4) letter_opener_web (~> 1.3.4)
license_finder (~> 6.0) license_finder (~> 6.0)
licensee (~> 8.9) licensee (~> 9.14.1)
lockbox (~> 0.3.3) lockbox (~> 0.3.3)
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.2) loofah (~> 2.2)
...@@ -1501,7 +1508,7 @@ DEPENDENCIES ...@@ -1501,7 +1508,7 @@ DEPENDENCIES
ruby-progressbar (~> 1.10) ruby-progressbar (~> 1.10)
ruby_parser (~> 3.15) ruby_parser (~> 3.15)
rubyzip (~> 2.0.0) rubyzip (~> 2.0.0)
rugged (~> 0.28) rugged (~> 1.0.1)
sanitize (~> 5.2.1) sanitize (~> 5.2.1)
sassc-rails (~> 2.1.0) sassc-rails (~> 2.1.0)
scss_lint (~> 0.59.0) scss_lint (~> 0.59.0)
......
...@@ -86,16 +86,16 @@ export default { ...@@ -86,16 +86,16 @@ export default {
return !this.disabled && this.listType !== ListType.closed; return !this.disabled && this.listType !== ListType.closed;
}, },
showMilestoneListDetails() { showMilestoneListDetails() {
return ( return this.listType === ListType.milestone && this.list.milestone && this.showListDetails;
this.listType === ListType.milestone &&
this.list.milestone &&
(!this.list.collapsed || !this.isSwimlanesHeader)
);
}, },
showAssigneeListDetails() { showAssigneeListDetails() {
return ( return this.listType === ListType.assignee && this.showListDetails;
this.listType === ListType.assignee && (!this.list.collapsed || !this.isSwimlanesHeader) },
); showIterationListDetails() {
return this.listType === ListType.iteration && this.showListDetails;
},
showListDetails() {
return !this.list.collapsed || !this.isSwimlanesHeader;
}, },
issuesCount() { issuesCount() {
return this.list.issuesCount; return this.list.issuesCount;
...@@ -218,6 +218,17 @@ export default { ...@@ -218,6 +218,17 @@ export default {
<gl-icon name="timer" /> <gl-icon name="timer" />
</span> </span>
<span
v-if="showIterationListDetails"
aria-hidden="true"
:class="{
'gl-mt-3 gl-rotate-90': list.collapsed,
'gl-mr-2': !list.collapsed,
}"
>
<gl-icon name="iteration" />
</span>
<a <a
v-if="showAssigneeListDetails" v-if="showAssigneeListDetails"
:href="list.assignee.webUrl" :href="list.assignee.webUrl"
......
...@@ -78,14 +78,16 @@ export default { ...@@ -78,14 +78,16 @@ export default {
return !this.disabled && this.listType !== ListType.closed; return !this.disabled && this.listType !== ListType.closed;
}, },
showMilestoneListDetails() { showMilestoneListDetails() {
return ( return this.list.type === 'milestone' && this.list.milestone && this.showListDetails;
this.list.type === 'milestone' &&
this.list.milestone &&
(this.list.isExpanded || !this.isSwimlanesHeader)
);
}, },
showAssigneeListDetails() { showAssigneeListDetails() {
return this.list.type === 'assignee' && (this.list.isExpanded || !this.isSwimlanesHeader); return this.list.type === 'assignee' && this.showListDetails;
},
showIterationListDetails() {
return this.listType === ListType.iteration && this.showListDetails;
},
showListDetails() {
return this.list.isExpanded || !this.isSwimlanesHeader;
}, },
issuesCount() { issuesCount() {
return this.list.issuesSize; return this.list.issuesSize;
...@@ -203,6 +205,17 @@ export default { ...@@ -203,6 +205,17 @@ export default {
<gl-icon name="timer" /> <gl-icon name="timer" />
</span> </span>
<span
v-if="showIterationListDetails"
aria-hidden="true"
:class="{
'gl-mt-3 gl-rotate-90': !list.isExpanded,
'gl-mr-2': list.isExpanded,
}"
>
<gl-icon name="iteration" />
</span>
<a <a
v-if="showAssigneeListDetails" v-if="showAssigneeListDetails"
:href="list.assignee.path" :href="list.assignee.path"
......
...@@ -5,17 +5,13 @@ import { __ } from '~/locale'; ...@@ -5,17 +5,13 @@ import { __ } from '~/locale';
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/sidebar/event_hub'; import eventHub from '~/sidebar/event_hub';
import { isScopedLabel } from '~/lib/utils/common_utils'; import { isScopedLabel } from '~/lib/utils/common_utils';
import { LIST } from '~/boards/constants'; import { LIST, ListType, ListTypeTitles } from '~/boards/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
// NOTE: need to revisit how we handle headerHeight, because we have so many different header and footer options. // NOTE: need to revisit how we handle headerHeight, because we have so many different header and footer options.
export default { export default {
headerHeight: process.env.NODE_ENV === 'development' ? '75px' : '40px', headerHeight: process.env.NODE_ENV === 'development' ? '75px' : '40px',
listSettingsText: __('List settings'), listSettingsText: __('List settings'),
assignee: 'assignee',
milestone: 'milestone',
label: 'label',
labelListText: __('Label'),
components: { components: {
GlButton, GlButton,
GlDrawer, GlDrawer,
...@@ -33,6 +29,11 @@ export default { ...@@ -33,6 +29,11 @@ export default {
default: false, default: false,
}, },
}, },
data() {
return {
ListType,
};
},
computed: { computed: {
...mapGetters(['isSidebarOpen', 'shouldUseGraphQL']), ...mapGetters(['isSidebarOpen', 'shouldUseGraphQL']),
...mapState(['activeId', 'sidebarType', 'boardLists']), ...mapState(['activeId', 'sidebarType', 'boardLists']),
...@@ -56,7 +57,7 @@ export default { ...@@ -56,7 +57,7 @@ export default {
return this.activeList.type || this.activeList.listType || null; return this.activeList.type || this.activeList.listType || null;
}, },
listTypeTitle() { listTypeTitle() {
return this.$options.labelListText; return ListTypeTitles[ListType.label];
}, },
showSidebar() { showSidebar() {
return this.sidebarType === LIST; return this.sidebarType === LIST;
...@@ -98,7 +99,7 @@ export default { ...@@ -98,7 +99,7 @@ export default {
> >
<template #header>{{ $options.listSettingsText }}</template> <template #header>{{ $options.listSettingsText }}</template>
<template v-if="isSidebarOpen"> <template v-if="isSidebarOpen">
<div v-if="boardListType === $options.label"> <div v-if="boardListType === ListType.label">
<label class="js-list-label gl-display-block">{{ listTypeTitle }}</label> <label class="js-list-label gl-display-block">{{ listTypeTitle }}</label>
<gl-label <gl-label
:title="activeListLabel.title" :title="activeListLabel.title"
......
import { __ } from '~/locale';
export const BoardType = { export const BoardType = {
project: 'project', project: 'project',
group: 'group', group: 'group',
...@@ -6,11 +8,19 @@ export const BoardType = { ...@@ -6,11 +8,19 @@ export const BoardType = {
export const ListType = { export const ListType = {
assignee: 'assignee', assignee: 'assignee',
milestone: 'milestone', milestone: 'milestone',
iteration: 'iteration',
backlog: 'backlog', backlog: 'backlog',
closed: 'closed', closed: 'closed',
label: 'label', label: 'label',
}; };
export const ListTypeTitles = {
assignee: __('Assignee'),
milestone: __('Milestone'),
iteration: __('Iteration'),
label: __('Label'),
};
export const inactiveId = 0; export const inactiveId = 0;
export const ISSUABLE = 'issuable'; export const ISSUABLE = 'issuable';
......
export default class ListIteration {
constructor(obj) {
this.id = obj.id;
this.title = obj.title;
this.state = obj.state;
this.webUrl = obj.web_url || obj.webUrl;
this.description = obj.description;
}
}
...@@ -5,6 +5,7 @@ import boardsStore from '../stores/boards_store'; ...@@ -5,6 +5,7 @@ import boardsStore from '../stores/boards_store';
import ListLabel from './label'; import ListLabel from './label';
import ListAssignee from './assignee'; import ListAssignee from './assignee';
import ListMilestone from './milestone'; import ListMilestone from './milestone';
import ListIteration from './iteration';
import 'ee_else_ce/boards/models/issue'; import 'ee_else_ce/boards/models/issue';
const TYPES = { const TYPES = {
...@@ -57,6 +58,9 @@ class List { ...@@ -57,6 +58,9 @@ class List {
} else if (IS_EE && obj.milestone) { } else if (IS_EE && obj.milestone) {
this.milestone = new ListMilestone(obj.milestone); this.milestone = new ListMilestone(obj.milestone);
this.title = this.milestone.title; this.title = this.milestone.title;
} else if (IS_EE && obj.iteration) {
this.iteration = new ListIteration(obj.iteration);
this.title = this.iteration.title;
} }
// doNotFetchIssues is a temporary workaround until issues are fetched using GraphQL on issue boards // doNotFetchIssues is a temporary workaround until issues are fetched using GraphQL on issue boards
......
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
export const GENERIC_ERROR = __('Something went wrong on our end. Please try again!'); export const GENERIC_ERROR = __('Something went wrong on our end. Please try again!');
...@@ -9,8 +9,8 @@ export const DIFF_FILE_HEADER = { ...@@ -9,8 +9,8 @@ export const DIFF_FILE_HEADER = {
}; };
export const DIFF_FILE = { export const DIFF_FILE = {
tooLarge: __('MRDiffFile|Changes are too large to be shown.'), tooLarge: s__('MRDiffFile|Changes are too large to be shown.'),
blobView: __('MRDiffFile|View file @ %{commitSha}'), blobView: s__('MRDiffFile|View file @ %{commitSha}'),
editInFork: __( editInFork: __(
"You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.", "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.",
), ),
......
...@@ -159,7 +159,12 @@ export default { ...@@ -159,7 +159,12 @@ export default {
[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode, hash }) { [types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode, hash }) {
const { latestDiff } = state; const { latestDiff } = state;
const discussionLineCodes = [discussion.line_code, ...(discussion.line_codes || [])]; const originalStartLineCode = discussion.original_position?.line_range?.start?.line_code;
const discussionLineCodes = [
discussion.line_code,
originalStartLineCode,
...(discussion.line_codes || []),
];
const fileHash = discussion.diff_file.file_hash; const fileHash = discussion.diff_file.file_hash;
const lineCheck = (line) => const lineCheck = (line) =>
discussionLineCodes.some( discussionLineCodes.some(
......
...@@ -13,6 +13,9 @@ export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = __( ...@@ -13,6 +13,9 @@ export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = __(
export const EDITOR_READY_EVENT = 'editor-ready'; export const EDITOR_READY_EVENT = 'editor-ready';
export const EDITOR_TYPE_CODE = 'vs.editor.ICodeEditor';
export const EDITOR_TYPE_DIFF = 'vs.editor.IDiffEditor';
// //
// EXTENSIONS' CONSTANTS // EXTENSIONS' CONSTANTS
// //
......
...@@ -6,7 +6,12 @@ import { registerLanguages } from '~/ide/utils'; ...@@ -6,7 +6,12 @@ import { registerLanguages } from '~/ide/utils';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths } from '~/lib/utils/url_utility';
import { uuids } from '~/diffs/utils/uuids'; import { uuids } from '~/diffs/utils/uuids';
import { clearDomElement } from './utils'; import { clearDomElement } from './utils';
import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX, EDITOR_READY_EVENT } from './constants'; import {
EDITOR_LITE_INSTANCE_ERROR_NO_EL,
URI_PREFIX,
EDITOR_READY_EVENT,
EDITOR_TYPE_DIFF,
} from './constants';
export default class EditorLite { export default class EditorLite {
constructor(options = {}) { constructor(options = {}) {
...@@ -29,15 +34,12 @@ export default class EditorLite { ...@@ -29,15 +34,12 @@ export default class EditorLite {
monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME); monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME);
} }
static updateModelLanguage(path, instance) { static getModelLanguage(path) {
if (!instance) return;
const model = instance.getModel();
const ext = `.${path.split('.').pop()}`; const ext = `.${path.split('.').pop()}`;
const language = monacoLanguages const language = monacoLanguages
.getLanguages() .getLanguages()
.find((lang) => lang.extensions.indexOf(ext) !== -1); .find((lang) => lang.extensions.indexOf(ext) !== -1);
const id = language ? language.id : 'plaintext'; return language ? language.id : 'plaintext';
monacoEditor.setModelLanguage(model, id);
} }
static pushToImportsArray(arr, toImport) { static pushToImportsArray(arr, toImport) {
...@@ -102,17 +104,91 @@ export default class EditorLite { ...@@ -102,17 +104,91 @@ export default class EditorLite {
}); });
} }
static createEditorModel({ blobPath, blobContent, blobGlobalId, instance } = {}) { static createEditorModel({
let model = null; blobPath,
blobContent,
blobOriginalContent,
blobGlobalId,
instance,
isDiff,
} = {}) {
if (!instance) { if (!instance) {
return null; return null;
} }
const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath); const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
const uri = Uri.file(uriFilePath); const uri = Uri.file(uriFilePath);
const existingModel = monacoEditor.getModel(uri); const existingModel = monacoEditor.getModel(uri);
model = existingModel || monacoEditor.createModel(blobContent, undefined, uri); const model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
instance.setModel(model); if (!isDiff) {
return model; instance.setModel(model);
return model;
}
const diffModel = {
original: monacoEditor.createModel(
blobOriginalContent,
EditorLite.getModelLanguage(model.uri.path),
),
modified: model,
};
instance.setModel(diffModel);
return diffModel;
}
static convertMonacoToELInstance = (inst) => {
const editorLiteInstanceAPI = {
updateModelLanguage: (path) => {
return EditorLite.instanceUpdateLanguage(inst, path);
},
use: (exts = []) => {
return EditorLite.instanceApplyExtension(inst, exts);
},
};
const handler = {
get(target, prop, receiver) {
if (Reflect.has(editorLiteInstanceAPI, prop)) {
return editorLiteInstanceAPI[prop];
}
return Reflect.get(target, prop, receiver);
},
};
return new Proxy(inst, handler);
};
static instanceUpdateLanguage(inst, path) {
const lang = EditorLite.getModelLanguage(path);
const model = inst.getModel();
return monacoEditor.setModelLanguage(model, lang);
}
static instanceApplyExtension(inst, exts = []) {
const extensions = [].concat(exts);
extensions.forEach((extension) => {
EditorLite.mixIntoInstance(extension, inst);
});
return inst;
}
static instanceRemoveFromRegistry(editor, instance) {
const index = editor.instances.findIndex((inst) => inst === instance);
editor.instances.splice(index, 1);
}
static instanceDisposeModels(editor, instance, model) {
const instanceModel = instance.getModel() || model;
if (!instanceModel) {
return;
}
if (instance.getEditorType() === EDITOR_TYPE_DIFF) {
const { original, modified } = instanceModel;
if (original) {
original.dispose();
}
if (modified) {
modified.dispose();
}
} else {
instanceModel.dispose();
}
} }
/** /**
...@@ -128,26 +204,38 @@ export default class EditorLite { ...@@ -128,26 +204,38 @@ export default class EditorLite {
el = undefined, el = undefined,
blobPath = '', blobPath = '',
blobContent = '', blobContent = '',
blobOriginalContent = '',
blobGlobalId = uuids()[0], blobGlobalId = uuids()[0],
extensions = [], extensions = [],
isDiff = false,
...instanceOptions ...instanceOptions
} = {}) { } = {}) {
EditorLite.prepareInstance(el); EditorLite.prepareInstance(el);
const instance = monacoEditor.create(el, { const createEditorFn = isDiff ? 'createDiffEditor' : 'create';
...this.options, const instance = EditorLite.convertMonacoToELInstance(
...instanceOptions, monacoEditor[createEditorFn].call(this, el, {
}); ...this.options,
...instanceOptions,
}),
);
const model = EditorLite.createEditorModel({ blobGlobalId, blobPath, blobContent, instance }); let model;
if (instanceOptions.model !== null) {
model = EditorLite.createEditorModel({
blobGlobalId,
blobOriginalContent,
blobPath,
blobContent,
instance,
isDiff,
});
}
instance.onDidDispose(() => { instance.onDidDispose(() => {
const index = this.instances.findIndex((inst) => inst === instance); EditorLite.instanceRemoveFromRegistry(this, instance);
this.instances.splice(index, 1); EditorLite.instanceDisposeModels(this, instance, model);
model.dispose();
}); });
instance.updateModelLanguage = (path) => EditorLite.updateModelLanguage(path, instance);
instance.use = (args) => this.use(args, instance);
EditorLite.manageDefaultExtensions(instance, el, extensions); EditorLite.manageDefaultExtensions(instance, el, extensions);
...@@ -155,23 +243,21 @@ export default class EditorLite { ...@@ -155,23 +243,21 @@ export default class EditorLite {
return instance; return instance;
} }
createDiffInstance(args) {
return this.createInstance({
...args,
isDiff: true,
});
}
dispose() { dispose() {
this.instances.forEach((instance) => instance.dispose()); this.instances.forEach((instance) => instance.dispose());
} }
use(exts = [], instance = null) { use(exts) {
const extensions = Array.isArray(exts) ? exts : [exts]; this.instances.forEach((inst) => {
const initExtensions = (inst) => { inst.use(exts);
extensions.forEach((extension) => { });
EditorLite.mixIntoInstance(extension, inst); return this;
});
};
if (instance) {
initExtensions(instance);
} else {
this.instances.forEach((inst) => {
initExtensions(inst);
});
}
} }
} }
<script> <script>
/* eslint-disable vue/no-v-html */ /* eslint-disable vue/no-v-html */
import { GlLoadingIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui'; import { GlLoadingIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { showLearnGitLabGroupItemPopover } from '~/onboarding_issues';
import { visitUrl } from '../../lib/utils/url_utility'; import { visitUrl } from '../../lib/utils/url_utility';
import identicon from '../../vue_shared/components/identicon.vue'; import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -77,11 +76,6 @@ export default { ...@@ -77,11 +76,6 @@ export default {
return this.group.microdata || {}; return this.group.microdata || {};
}, },
}, },
mounted() {
if (this.group.name === 'Learn GitLab') {
showLearnGitLabGroupItemPopover(this.group.id);
}
},
methods: { methods: {
onClickRowGroup(e) { onClickRowGroup(e) {
const NO_EXPAND_CLS = 'no-expand'; const NO_EXPAND_CLS = 'no-expand';
......
...@@ -95,10 +95,7 @@ export default { ...@@ -95,10 +95,7 @@ export default {
</script> </script>
<template> <template>
<div> <div class="gl-mb-3">
<!-- helpHtml is trusted input -->
<div v-if="helpHtml" v-safe-html:[$options.helpHtmlConfig]="helpHtml"></div>
<override-dropdown <override-dropdown
v-if="defaultState !== null" v-if="defaultState !== null"
:inherit-from-id="defaultState.id" :inherit-from-id="defaultState.id"
...@@ -107,80 +104,91 @@ export default { ...@@ -107,80 +104,91 @@ export default {
@change="setOverride" @change="setOverride"
/> />
<active-checkbox v-if="propsSource.showActive" :key="`${currentKey}-active-checkbox`" /> <div class="row">
<jira-trigger-fields <div class="col-lg-4"></div>
v-if="isJira"
:key="`${currentKey}-jira-trigger-fields`" <div class="col-lg-8">
v-bind="propsSource.triggerFieldsProps" <!-- helpHtml is trusted input -->
/> <div v-if="helpHtml" v-safe-html:[$options.helpHtmlConfig]="helpHtml"></div>
<trigger-fields
v-else-if="propsSource.triggerEvents.length" <active-checkbox v-if="propsSource.showActive" :key="`${currentKey}-active-checkbox`" />
:key="`${currentKey}-trigger-fields`" <jira-trigger-fields
:events="propsSource.triggerEvents" v-if="isJira"
:type="propsSource.type" :key="`${currentKey}-jira-trigger-fields`"
/> v-bind="propsSource.triggerFieldsProps"
<dynamic-field />
v-for="field in propsSource.fields" <trigger-fields
:key="`${currentKey}-${field.name}`" v-else-if="propsSource.triggerEvents.length"
v-bind="field" :key="`${currentKey}-trigger-fields`"
/> :events="propsSource.triggerEvents"
<jira-issues-fields :type="propsSource.type"
v-if="showJiraIssuesFields" />
:key="`${currentKey}-jira-issues-fields`" <dynamic-field
v-bind="propsSource.jiraIssuesProps" v-for="field in propsSource.fields"
/> :key="`${currentKey}-${field.name}`"
<div v-if="isEditable" class="footer-block row-content-block"> v-bind="field"
<template v-if="isInstanceOrGroupLevel"> />
<gl-button <jira-issues-fields
v-gl-modal.confirmSaveIntegration v-if="showJiraIssuesFields"
category="primary" :key="`${currentKey}-jira-issues-fields`"
variant="success" v-bind="propsSource.jiraIssuesProps"
:loading="isSaving" />
:disabled="isDisabled" <div v-if="isEditable" class="footer-block row-content-block">
data-qa-selector="save_changes_button" <template v-if="isInstanceOrGroupLevel">
> <gl-button
{{ __('Save changes') }} v-gl-modal.confirmSaveIntegration
</gl-button> category="primary"
<confirmation-modal @submit="onSaveClick" /> variant="success"
</template> :loading="isSaving"
<gl-button :disabled="isDisabled"
v-else data-qa-selector="save_changes_button"
category="primary" >
variant="success" {{ __('Save changes') }}
type="submit" </gl-button>
:loading="isSaving" <confirmation-modal @submit="onSaveClick" />
:disabled="isDisabled" </template>
data-qa-selector="save_changes_button" <gl-button
@click.prevent="onSaveClick" v-else
> category="primary"
{{ __('Save changes') }} variant="success"
</gl-button> type="submit"
:loading="isSaving"
:disabled="isDisabled"
data-qa-selector="save_changes_button"
@click.prevent="onSaveClick"
>
{{ __('Save changes') }}
</gl-button>
<gl-button <gl-button
v-if="propsSource.canTest" v-if="propsSource.canTest"
:loading="isTesting" :loading="isTesting"
:disabled="isDisabled" :disabled="isDisabled"
:href="propsSource.testPath" :href="propsSource.testPath"
@click.prevent="onTestClick" @click.prevent="onTestClick"
> >
{{ __('Test settings') }} {{ __('Test settings') }}
</gl-button> </gl-button>
<template v-if="showReset"> <template v-if="showReset">
<gl-button <gl-button
v-gl-modal.confirmResetIntegration v-gl-modal.confirmResetIntegration
category="secondary" category="secondary"
variant="default" variant="default"
:loading="isResetting" :loading="isResetting"
:disabled="isDisabled" :disabled="isDisabled"
data-testid="reset-button" data-testid="reset-button"
> >
{{ __('Reset') }} {{ __('Reset') }}
</gl-button> </gl-button>
<reset-confirmation-modal @reset="onResetClick" /> <reset-confirmation-modal @reset="onResetClick" />
</template> </template>
<gl-button class="btn-cancel" :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button> <gl-button class="btn-cancel" :href="propsSource.cancelPath">{{
__('Cancel')
}}</gl-button>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -54,11 +54,6 @@ export default { ...@@ -54,11 +54,6 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
runnerHelpUrl: {
type: String,
required: false,
default: null,
},
deploymentHelpUrl: { deploymentHelpUrl: {
type: String, type: String,
required: false, required: false,
...@@ -250,7 +245,6 @@ export default { ...@@ -250,7 +245,6 @@ export default {
v-if="shouldRenderSharedRunnerLimitWarning" v-if="shouldRenderSharedRunnerLimitWarning"
:quota-used="job.runners.quota.used" :quota-used="job.runners.quota.used"
:quota-limit="job.runners.quota.limit" :quota-limit="job.runners.quota.limit"
:runners-path="runnerHelpUrl"
:project-path="projectPath" :project-path="projectPath"
:subscriptions-more-minutes-url="subscriptionsMoreMinutesUrl" :subscriptions-more-minutes-url="subscriptionsMoreMinutesUrl"
/> />
...@@ -330,7 +324,6 @@ export default { ...@@ -330,7 +324,6 @@ export default {
'right-sidebar-collapsed': !isSidebarOpen, 'right-sidebar-collapsed': !isSidebarOpen,
}" }"
:artifact-help-url="artifactHelpUrl" :artifact-help-url="artifactHelpUrl"
:runner-help-url="runnerHelpUrl"
data-testid="job-sidebar" data-testid="job-sidebar"
/> />
</div> </div>
......
<script> <script>
import { GlBadge } from '@gitlab/ui';
export default { export default {
components: {
GlBadge,
},
props: { props: {
duration: { duration: {
type: String, type: String,
...@@ -9,7 +14,7 @@ export default { ...@@ -9,7 +14,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="log-duration-badge rounded align-self-start px-2 ml-2 flex-shrink-0 ws-normal"> <gl-badge>
{{ duration }} {{ duration }}
</div> </gl-badge>
</template> </template>
...@@ -41,11 +41,6 @@ export default { ...@@ -41,11 +41,6 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
runnerHelpUrl: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
...mapGetters(['hasForwardDeploymentFailure']), ...mapGetters(['hasForwardDeploymentFailure']),
...@@ -135,7 +130,7 @@ export default { ...@@ -135,7 +130,7 @@ export default {
<gl-icon :size="14" name="external-link" /> <gl-icon :size="14" name="external-link" />
</gl-link> </gl-link>
</div> </div>
<job-sidebar-details-container :runner-help-url="runnerHelpUrl" /> <job-sidebar-details-container />
<artifacts-block v-if="hasArtifact" :artifact="job.artifact" :help-url="artifactHelpUrl" /> <artifacts-block v-if="hasArtifact" :artifact="job.artifact" :help-url="artifactHelpUrl" />
<trigger-block v-if="hasTriggers" :trigger="job.trigger" /> <trigger-block v-if="hasTriggers" :trigger="job.trigger" />
<commit-block <commit-block
......
...@@ -3,6 +3,7 @@ import { mapState } from 'vuex'; ...@@ -3,6 +3,7 @@ import { mapState } from 'vuex';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import { helpPagePath } from '~/helpers/help_page_helper';
import DetailRow from './sidebar_detail_row.vue'; import DetailRow from './sidebar_detail_row.vue';
export default { export default {
...@@ -11,13 +12,6 @@ export default { ...@@ -11,13 +12,6 @@ export default {
DetailRow, DetailRow,
}, },
mixins: [timeagoMixin], mixins: [timeagoMixin],
props: {
runnerHelpUrl: {
type: String,
required: false,
default: '',
},
},
computed: { computed: {
...mapState(['job']), ...mapState(['job']),
coverage() { coverage() {
...@@ -51,6 +45,11 @@ export default { ...@@ -51,6 +45,11 @@ export default {
queued() { queued() {
return timeIntervalInWords(this.job.queued); return timeIntervalInWords(this.job.queued);
}, },
runnerHelpUrl() {
return helpPagePath('ci/runners/README.html', {
anchor: 'set-maximum-job-timeout-for-a-runner',
});
},
runnerId() { runnerId() {
return `${this.job.runner.description} (#${this.job.runner.id})`; return `${this.job.runner.description} (#${this.job.runner.id})`;
}, },
......
...@@ -13,7 +13,6 @@ export default () => { ...@@ -13,7 +13,6 @@ export default () => {
const { const {
artifactHelpUrl, artifactHelpUrl,
deploymentHelpUrl, deploymentHelpUrl,
runnerHelpUrl,
runnerSettingsUrl, runnerSettingsUrl,
variablesSettingsUrl, variablesSettingsUrl,
subscriptionsMoreMinutesUrl, subscriptionsMoreMinutesUrl,
...@@ -39,7 +38,6 @@ export default () => { ...@@ -39,7 +38,6 @@ export default () => {
props: { props: {
artifactHelpUrl, artifactHelpUrl,
deploymentHelpUrl, deploymentHelpUrl,
runnerHelpUrl,
runnerSettingsUrl, runnerSettingsUrl,
variablesSettingsUrl, variablesSettingsUrl,
subscriptionsMoreMinutesUrl, subscriptionsMoreMinutesUrl,
......
...@@ -143,6 +143,9 @@ export default { ...@@ -143,6 +143,9 @@ export default {
trackingLabel() { trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`); return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
}, },
hasCloseAndCommentButton() {
return !this.glFeatures.removeCommentCloseReopen;
},
}, },
watch: { watch: {
note(newNote) { note(newNote) {
...@@ -405,7 +408,7 @@ export default { ...@@ -405,7 +408,7 @@ export default {
</div> </div>
<gl-button <gl-button
v-if="canToggleIssueState" v-if="hasCloseAndCommentButton && canToggleIssueState"
:loading="isToggleStateButtonLoading" :loading="isToggleStateButtonLoading"
category="secondary" category="secondary"
:variant="buttonVariant" :variant="buttonVariant"
......
import $ from 'jquery';
import { parseBoolean, getCookie, setCookie, removeCookie } from '~/lib/utils/common_utils';
import { __, sprintf } from '~/locale';
import Tracking from '~/tracking';
const COOKIE_NAME = 'onboarding_issues_settings';
const POPOVER_LOCATIONS = {
GROUPS_SHOW: 'groups#show',
PROJECTS_SHOW: 'projects#show',
ISSUES_INDEX: 'issues#index',
};
const removeLearnGitLabCookie = () => {
removeCookie(COOKIE_NAME);
};
function disposePopover(event) {
event.preventDefault();
this.popover('dispose');
removeLearnGitLabCookie();
Tracking.event('Growth::Conversion::Experiment::OnboardingIssues', 'dismiss_popover');
}
const showPopover = (el, path, footer, options) => {
// Cookie value looks like `{ 'groups#show': true, 'projects#show': true, 'issues#index': true }`. When it doesn't exist, don't show the popover.
const cookie = getCookie(COOKIE_NAME);
if (!cookie) return;
// When the popover action has already been taken, don't show the popover.
const settings = JSON.parse(cookie);
if (!parseBoolean(settings[path])) return;
const defaultOptions = {
boundary: 'window',
html: true,
placement: 'top',
template: `<div class="gl-popover popover blue learn-gitlab d-none d-xl-block" role="tooltip">
<div class="arrow"></div>
<div class="js-close-learn-gitlab gl-font-weight-bold gl-line-height-normal float-right gl-cursor-pointer gl-font-base gl-text-white gl-opacity-10 gl-p-3">&#10005</div>
<div class="popover-body gl-font-base"></div>
<div class="gl-font-weight-bold gl-text-right gl-text-white gl-p-3 gl-pt-0">${footer}</div>
</div>`,
};
// When one of the popovers is dismissed, remove the cookie.
const closeButton = () => document.querySelector('.js-close-learn-gitlab');
// We still have to use jQuery, since Bootstrap's Popover is based on jQuery.
const jQueryEl = $(el);
const clickCloseButton = disposePopover.bind(jQueryEl);
jQueryEl
.popover({ ...defaultOptions, ...options })
.on('inserted.bs.popover', () => closeButton().addEventListener('click', clickCloseButton))
.on('hide.bs.dropdown', () => closeButton().removeEventListener('click', clickCloseButton))
.popover('show');
// The previous popover actions have been taken, don't show those popovers anymore.
Object.keys(settings).forEach((pathSetting) => {
if (path !== pathSetting) {
settings[pathSetting] = false;
} else {
setCookie(COOKIE_NAME, settings);
}
});
// The final popover action will be taken on click, we then no longer need the cookie.
if (path === POPOVER_LOCATIONS.ISSUES_INDEX) {
el.addEventListener('click', removeLearnGitLabCookie);
}
};
export const showLearnGitLabGroupItemPopover = (id) => {
const el = document.querySelector(`#group-${id} .group-text a`);
if (!el) return;
const options = {
content: __(
'Here are all your projects in your group, including the one you just created. To start, let’s take a look at your personalized learning project which will help you learn about GitLab at your own pace.',
),
};
showPopover(el, POPOVER_LOCATIONS.GROUPS_SHOW, '1 / 2', options);
};
export const showLearnGitLabProjectPopover = () => {
// Do not show a popover if we are not viewing the 'Learn GitLab' project.
if (!window.location.pathname.includes('learn-gitlab')) return;
const el = document.querySelector('a.shortcuts-issues');
if (!el) return;
const options = {
content: sprintf(
__(
'Go to %{strongStart}Issues%{strongEnd} &gt; %{strongStart}Boards%{strongEnd} to access your personalized learning issue board.',
),
{ strongStart: '<strong>', strongEnd: '</strong>' },
false,
),
};
showPopover(el, POPOVER_LOCATIONS.PROJECTS_SHOW, '2 / 2', options);
};
export const showLearnGitLabIssuesPopover = () => {
// Do not show a popover if we are not viewing the 'Learn GitLab' project.
if (!window.location.pathname.includes('learn-gitlab')) return;
const el = document.querySelector('a[data-qa-selector="issue_boards_link"]');
if (!el) return;
const options = {
content: sprintf(
__(
'Go to %{strongStart}Issues%{strongEnd} &gt; %{strongStart}Boards%{strongEnd} to access your personalized learning issue board.',
),
{ strongStart: '<strong>', strongEnd: '</strong>' },
false,
),
};
showPopover(el, POPOVER_LOCATIONS.ISSUES_INDEX, '2 / 2', options);
};
...@@ -9,7 +9,6 @@ import { FILTERED_SEARCH } from '~/pages/constants'; ...@@ -9,7 +9,6 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants';
import initIssuablesList from '~/issues_list'; import initIssuablesList from '~/issues_list';
import initManualOrdering from '~/manual_ordering'; import initManualOrdering from '~/manual_ordering';
import { showLearnGitLabIssuesPopover } from '~/onboarding_issues';
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
...@@ -25,4 +24,3 @@ new UsersSelect(); ...@@ -25,4 +24,3 @@ new UsersSelect();
initManualOrdering(); initManualOrdering();
initIssuablesList(); initIssuablesList();
showLearnGitLabIssuesPopover();
...@@ -7,7 +7,6 @@ import BlobViewer from '~/blob/viewer/index'; ...@@ -7,7 +7,6 @@ import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities'; import Activities from '~/activities';
import initReadMore from '~/read_more'; import initReadMore from '~/read_more';
import leaveByUrl from '~/namespaces/leave_by_url'; import leaveByUrl from '~/namespaces/leave_by_url';
import { showLearnGitLabProjectPopover } from '~/onboarding_issues';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initVueNotificationsDropdown from '~/notifications'; import initVueNotificationsDropdown from '~/notifications';
...@@ -41,8 +40,6 @@ if (document.querySelector('.project-show-activity')) { ...@@ -41,8 +40,6 @@ if (document.querySelector('.project-show-activity')) {
leaveByUrl('project'); leaveByUrl('project');
showLearnGitLabProjectPopover();
if (gon.features?.vueNotificationDropdown) { if (gon.features?.vueNotificationDropdown) {
initVueNotificationsDropdown(); initVueNotificationsDropdown();
} else { } else {
......
<script>
import CommitForm from './commit_form.vue';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
export default {
alertTexts: {
[COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'),
[COMMIT_SUCCESS]: __('Your changes have been successfully committed.'),
},
i18n: {
defaultCommitMessage: __('Update %{sourcePath} file'),
},
components: {
CommitForm,
},
inject: ['projectFullPath', 'ciConfigPath', 'defaultBranch', 'newMergeRequestPath'],
props: {
ciFileContent: {
type: String,
required: true,
},
},
data() {
return {
commit: {},
isSaving: false,
};
},
apollo: {
commitSha: {
query: getCommitSha,
},
},
computed: {
defaultCommitMessage() {
return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath });
},
},
methods: {
redirectToNewMergeRequest(sourceBranch) {
const url = mergeUrlParams(
{
[MR_SOURCE_BRANCH]: sourceBranch,
[MR_TARGET_BRANCH]: this.defaultBranch,
},
this.newMergeRequestPath,
);
redirectTo(url);
},
async onCommitSubmit({ message, branch, openMergeRequest }) {
this.isSaving = true;
try {
const {
data: {
commitCreate: { errors },
},
} = await this.$apollo.mutate({
mutation: commitCIFile,
variables: {
projectPath: this.projectFullPath,
branch,
startBranch: this.defaultBranch,
message,
filePath: this.ciConfigPath,
content: this.ciFileContent,
lastCommitId: this.commitSha,
},
update(store, { data }) {
const commitSha = data?.commitCreate?.commit?.sha;
if (commitSha) {
store.writeQuery({ query: getCommitSha, data: { commitSha } });
}
},
});
if (errors?.length) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors });
} else if (openMergeRequest) {
this.redirectToNewMergeRequest(branch);
} else {
this.$emit('commit', { type: COMMIT_SUCCESS });
}
} catch (error) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: [error?.message] });
} finally {
this.isSaving = false;
}
},
onCommitCancel() {
this.$emit('resetContent');
},
},
};
</script>
<template>
<commit-form
:default-branch="defaultBranch"
:default-message="defaultCommitMessage"
:is-saving="isSaving"
@cancel="onCommitCancel"
@submit="onCommitSubmit"
/>
</template>
<script>
import ValidationSegment from './validation_segment.vue';
export default {
validationSegmentClasses:
'gl-p-5 gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base',
components: {
ValidationSegment,
},
props: {
ciConfigData: {
type: Object,
required: true,
},
isCiConfigDataLoading: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div class="gl-mb-5">
<validation-segment
:class="$options.validationSegmentClasses"
:loading="isCiConfigDataLoading"
:ci-config="ciConfigData"
/>
</div>
</template>
<script>
import { GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import CiLint from './lint/ci_lint.vue';
import EditorTab from './ui/editor_tab.vue';
import TextEditor from './text_editor.vue';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
export default {
i18n: {
tabEdit: s__('Pipelines|Write pipeline configuration'),
tabGraph: s__('Pipelines|Visualize'),
tabLint: s__('Pipelines|Lint'),
},
components: {
CiLint,
EditorTab,
GlLoadingIcon,
GlTab,
GlTabs,
PipelineGraph,
TextEditor,
},
mixins: [glFeatureFlagsMixin()],
props: {
ciConfigData: {
type: Object,
required: true,
},
ciFileContent: {
type: String,
required: true,
},
isCiConfigDataLoading: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<gl-tabs class="file-editor gl-mb-3">
<editor-tab :title="$options.i18n.tabEdit" lazy data-testid="editor-tab">
<text-editor :value="ciFileContent" v-on="$listeners" />
</editor-tab>
<gl-tab
v-if="glFeatures.ciConfigVisualizationTab"
:title="$options.i18n.tabGraph"
lazy
data-testid="visualization-tab"
>
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<pipeline-graph v-else :pipeline-data="ciConfigData" />
</gl-tab>
<editor-tab :title="$options.i18n.tabLint" data-testid="lint-tab">
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<ci-lint v-else :ci-config="ciConfigData" />
</editor-tab>
</gl-tabs>
</template>
...@@ -2,26 +2,29 @@ ...@@ -2,26 +2,29 @@
import EditorLite from '~/vue_shared/components/editor_lite.vue'; import EditorLite from '~/vue_shared/components/editor_lite.vue';
import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext'; import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext';
import { EDITOR_READY_EVENT } from '~/editor/constants'; import { EDITOR_READY_EVENT } from '~/editor/constants';
import getCommitSha from '../graphql/queries/client/commit_sha.graphql';
export default { export default {
components: { components: {
EditorLite, EditorLite,
}, },
inject: ['projectPath', 'projectNamespace'], inject: ['ciConfigPath', 'projectPath', 'projectNamespace'],
inheritAttrs: false, inheritAttrs: false,
props: { data() {
ciConfigPath: { return {
type: String, commitSha: '',
required: true, };
}, },
apollo: {
commitSha: { commitSha: {
type: String, query: getCommitSha,
required: false,
default: null,
}, },
}, },
methods: { methods: {
onEditorReady() { onCiConfigUpdate(content) {
this.$emit('updateCiConfig', content);
},
registerCiSchema() {
const editorInstance = this.$refs.editor.getEditor(); const editorInstance = this.$refs.editor.getEditor();
editorInstance.use(new CiSchemaExtension()); editorInstance.use(new CiSchemaExtension());
...@@ -41,7 +44,8 @@ export default { ...@@ -41,7 +44,8 @@ export default {
ref="editor" ref="editor"
:file-name="ciConfigPath" :file-name="ciConfigPath"
v-bind="$attrs" v-bind="$attrs"
@[$options.readyEvent]="onEditorReady" @[$options.readyEvent]="registerCiSchema"
@input="onCiConfigUpdate"
v-on="$listeners" v-on="$listeners"
/> />
</div> </div>
......
export const CI_CONFIG_STATUS_VALID = 'VALID'; export const CI_CONFIG_STATUS_VALID = 'VALID';
export const CI_CONFIG_STATUS_INVALID = 'INVALID'; export const CI_CONFIG_STATUS_INVALID = 'INVALID';
export const COMMIT_FAILURE = 'COMMIT_FAILURE';
export const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
export const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
export const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
mutation commitCIFileMutation( mutation commitCIFile(
$projectPath: ID! $projectPath: ID!
$branch: String! $branch: String!
$startBranch: String $startBranch: String
......
...@@ -15,14 +15,13 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -15,14 +15,13 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
} }
const { const {
// props // Add to apollo cache as it can be updated by future queries
ciConfigPath,
commitSha, commitSha,
// Add to provide/inject API for static values
ciConfigPath,
defaultBranch, defaultBranch,
newMergeRequestPath,
// `provide/inject` data
lintHelpPagePath, lintHelpPagePath,
newMergeRequestPath,
projectFullPath, projectFullPath,
projectPath, projectPath,
projectNamespace, projectNamespace,
...@@ -35,25 +34,27 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -35,25 +34,27 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
defaultClient: createDefaultClient(resolvers, { typeDefs }), defaultClient: createDefaultClient(resolvers, { typeDefs }),
}); });
apolloProvider.clients.defaultClient.cache.writeData({
data: {
commitSha,
},
});
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
provide: { provide: {
ciConfigPath,
defaultBranch,
lintHelpPagePath, lintHelpPagePath,
newMergeRequestPath,
projectFullPath, projectFullPath,
projectPath, projectPath,
projectNamespace, projectNamespace,
ymlHelpPagePath, ymlHelpPagePath,
}, },
render(h) { render(h) {
return h(PipelineEditorApp, { return h(PipelineEditorApp);
props: {
ciConfigPath,
commitSha,
defaultBranch,
newMergeRequestPath,
},
});
}, },
}); });
}; };
<script>
import CommitSection from './components/commit/commit_section.vue';
import PipelineEditorTabs from './components/pipeline_editor_tabs.vue';
import PipelineEditorHeader from './components/header/pipeline_editor_header.vue';
export default {
components: {
CommitSection,
PipelineEditorHeader,
PipelineEditorTabs,
},
props: {
ciConfigData: {
type: Object,
required: true,
},
ciFileContent: {
type: String,
required: true,
},
isCiConfigDataLoading: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div>
<pipeline-editor-header
:ci-config-data="ciConfigData"
:is-ci-config-data-loading="isCiConfigDataLoading"
/>
<pipeline-editor-tabs
:ci-config-data="ciConfigData"
:ci-file-content="ciFileContent"
:is-ci-config-data-loading="isCiConfigDataLoading"
v-on="$listeners"
/>
<commit-section :ci-file-content="ciFileContent" v-on="$listeners" />
</div>
</template>
...@@ -28,7 +28,6 @@ export default { ...@@ -28,7 +28,6 @@ export default {
return { return {
currentPipeline: null, currentPipeline: null,
loadingPipelineId: null, loadingPipelineId: null,
minWidth: 0,
pipelineExpanded: false, pipelineExpanded: false,
}; };
}, },
...@@ -40,6 +39,7 @@ export default { ...@@ -40,6 +39,7 @@ export default {
'gl-pl-3', 'gl-pl-3',
'gl-mb-5', 'gl-mb-5',
], ],
minWidth: `${ONE_COL_WIDTH}px`,
computed: { computed: {
columnClass() { columnClass() {
const positionValues = { const positionValues = {
...@@ -48,12 +48,6 @@ export default { ...@@ -48,12 +48,6 @@ export default {
}; };
return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`; return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
}, },
graphPosition() {
return this.isUpstream ? 'left' : 'right';
},
isUpstream() {
return this.type === UPSTREAM;
},
computedTitleClasses() { computedTitleClasses() {
const positionalClasses = this.isUpstream const positionalClasses = this.isUpstream
? ['gl-w-full', 'gl-text-right', 'gl-linked-pipeline-padding'] ? ['gl-w-full', 'gl-text-right', 'gl-linked-pipeline-padding']
...@@ -61,6 +55,12 @@ export default { ...@@ -61,6 +55,12 @@ export default {
return [...this.$options.titleClasses, ...positionalClasses]; return [...this.$options.titleClasses, ...positionalClasses];
}, },
graphPosition() {
return this.isUpstream ? 'left' : 'right';
},
isUpstream() {
return this.type === UPSTREAM;
},
}, },
methods: { methods: {
getPipelineData(pipeline) { getPipelineData(pipeline) {
...@@ -105,7 +105,6 @@ export default { ...@@ -105,7 +105,6 @@ export default {
if (this.currentPipeline?.id === pipeline.id) { if (this.currentPipeline?.id === pipeline.id) {
this.pipelineExpanded = false; this.pipelineExpanded = false;
this.currentPipeline = null; this.currentPipeline = null;
this.minWidth = 0;
return; return;
} }
...@@ -120,11 +119,6 @@ export default { ...@@ -120,11 +119,6 @@ export default {
*/ */
this.pipelineExpanded = true; this.pipelineExpanded = true;
/*
Min-width is set manually for timing reasons.
*/
this.minWidth = `${ONE_COL_WIDTH}px`;
this.getPipelineData(pipeline); this.getPipelineData(pipeline);
}, },
onDownstreamHovered(jobName) { onDownstreamHovered(jobName) {
...@@ -138,6 +132,9 @@ export default { ...@@ -138,6 +132,9 @@ export default {
this.$emit('pipelineExpandToggle', jobName, expanded); this.$emit('pipelineExpandToggle', jobName, expanded);
}, },
showDownstreamContainer(id) {
return !this.isUpstream && (this.isExpanded(id) || this.isLoadingPipeline(id));
},
}, },
}; };
</script> </script>
...@@ -167,12 +164,12 @@ export default { ...@@ -167,12 +164,12 @@ export default {
@pipelineExpandToggle="onPipelineExpandToggle" @pipelineExpandToggle="onPipelineExpandToggle"
/> />
<div <div
v-if="isExpanded(pipeline.id) && !isUpstream" v-if="showDownstreamContainer(pipeline.id)"
:style="{ minWidth }" :style="{ minWidth: $options.minWidth }"
class="gl-display-inline-block" class="gl-display-inline-block"
> >
<pipeline-graph <pipeline-graph
v-if="currentPipeline" v-if="isExpanded(pipeline.id)"
:type="type" :type="type"
class="d-inline-block gl-mt-n2" class="d-inline-block gl-mt-n2"
:pipeline="currentPipeline" :pipeline="currentPipeline"
......
...@@ -74,13 +74,15 @@ export default { ...@@ -74,13 +74,15 @@ export default {
<div v-else> <div v-else>
<gl-alert <gl-alert
v-if="showAlert" v-if="showAlert"
class="gl-w-max-content gl-ml-4" class="gl-ml-4 gl-mb-4"
:primary-button-text="$options.i18n.showLinksAnyways" :primary-button-text="$options.i18n.showLinksAnyways"
@primaryAction="overrideShowLinks" @primaryAction="overrideShowLinks"
@dismiss="dismissAlert" @dismiss="dismissAlert"
> >
{{ $options.i18n.tooManyJobs }} {{ $options.i18n.tooManyJobs }}
</gl-alert> </gl-alert>
<slot></slot> <div class="gl-display-flex gl-relative">
<slot></slot>
</div>
</div> </div>
</template> </template>
...@@ -76,8 +76,8 @@ export default { ...@@ -76,8 +76,8 @@ export default {
class="d-inline-block" class="d-inline-block"
> >
<!-- use d-flex so that slot can be appropriately styled --> <!-- use d-flex so that slot can be appropriately styled -->
<span class="d-flex"> <span class="gl-display-flex gl-align-items-center">
<reviewer-avatar :user="user" :img-size="32" :issuable-type="issuableType" /> <reviewer-avatar :user="user" :img-size="24" :issuable-type="issuableType" />
<slot :user="user"></slot> <slot :user="user"></slot>
</span> </span>
</gl-link> </gl-link>
......
...@@ -46,6 +46,9 @@ export default { ...@@ -46,6 +46,9 @@ export default {
assignSelf() { assignSelf() {
this.$emit('assign-self'); this.$emit('assign-self');
}, },
requestReview(data) {
this.$emit('request-review', data);
},
}, },
}; };
</script> </script>
...@@ -66,6 +69,7 @@ export default { ...@@ -66,6 +69,7 @@ export default {
:users="sortedReviewers" :users="sortedReviewers"
:root-path="rootPath" :root-path="rootPath"
:issuable-type="issuableType" :issuable-type="issuableType"
@request-review="requestReview"
/> />
</div> </div>
</div> </div>
......
...@@ -83,6 +83,9 @@ export default { ...@@ -83,6 +83,9 @@ export default {
return new Flash(__('Error occurred when saving reviewers')); return new Flash(__('Error occurred when saving reviewers'));
}); });
}, },
requestReview(data) {
this.mediator.requestReview(data);
},
}, },
}; };
</script> </script>
...@@ -101,6 +104,7 @@ export default { ...@@ -101,6 +104,7 @@ export default {
:editable="store.editable" :editable="store.editable"
:issuable-type="issuableType" :issuable-type="issuableType"
class="value" class="value"
@request-review="requestReview"
/> />
</div> </div>
</template> </template>
<script> <script>
// NOTE! For the first iteration, we are simply copying the implementation of Assignees // NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736 // It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import ReviewerAvatarLink from './reviewer_avatar_link.vue'; import ReviewerAvatarLink from './reviewer_avatar_link.vue';
...@@ -8,8 +9,13 @@ const DEFAULT_RENDER_COUNT = 5; ...@@ -8,8 +9,13 @@ const DEFAULT_RENDER_COUNT = 5;
export default { export default {
components: { components: {
GlButton,
GlIcon,
ReviewerAvatarLink, ReviewerAvatarLink,
}, },
directives: {
GlTooltip: GlTooltipDirective,
},
props: { props: {
users: { users: {
type: Array, type: Array,
...@@ -28,6 +34,8 @@ export default { ...@@ -28,6 +34,8 @@ export default {
data() { data() {
return { return {
showLess: true, showLess: true,
loading: false,
requestedReviewSuccess: false,
}; };
}, },
computed: { computed: {
...@@ -61,43 +69,53 @@ export default { ...@@ -61,43 +69,53 @@ export default {
toggleShowLess() { toggleShowLess() {
this.showLess = !this.showLess; this.showLess = !this.showLess;
}, },
reRequestReview(userId) {
this.loading = true;
this.$emit('request-review', { userId, callback: this.requestReviewComplete });
},
requestReviewComplete(success) {
if (success) {
this.requestedReviewSuccess = true;
setTimeout(() => {
this.requestedReviewSuccess = false;
}, 1500);
}
this.loading = false;
},
}, },
}; };
</script> </script>
<template> <template>
<reviewer-avatar-link <div>
v-if="hasOneUser" <div
#default="{ user }" v-for="(user, index) in users"
tooltip-placement="left" :key="user.id"
:tooltip-has-name="false" :class="{ 'gl-mb-3': index !== users.length - 1 }"
:user="firstUser" data-testid="reviewer"
:root-path="rootPath" >
:issuable-type="issuableType" <reviewer-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType">
> <div class="gl-ml-3">@{{ user.username }}</div>
<div class="gl-ml-3 gl-line-height-normal"> </reviewer-avatar-link>
<div class="author">{{ user.name }}</div> <gl-icon
<div class="username">{{ username }}</div> v-if="requestedReviewSuccess"
</div> :size="24"
</reviewer-avatar-link> name="check"
<div v-else> class="float-right gl-text-green-500"
<div class="user-list"> />
<div v-for="user in uncollapsedUsers" :key="user.id" class="user-item"> <gl-button
<reviewer-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType" /> v-else-if="user.can_update_merge_request && user.reviewed"
</div> v-gl-tooltip.left
</div> :title="__('Re-request review')"
<div v-if="renderShowMoreSection" class="user-list-more"> :loading="loading"
<button class="float-right gl-text-gray-500!"
type="button" size="small"
class="btn-link" icon="redo"
data-qa-selector="more_reviewers_link" variant="link"
@click="toggleShowLess" @click="reRequestReview(user.id)"
> />
<template v-if="showLess">
{{ hiddenReviewersLabel }}
</template>
<template v-else>{{ __('- show less') }}</template>
</button>
</div> </div>
</div> </div>
</template> </template>
...@@ -42,7 +42,7 @@ export default { ...@@ -42,7 +42,7 @@ export default {
buttonClasses() { buttonClasses() {
return this.collapsed return this.collapsed
? 'btn-blank btn-todo sidebar-collapsed-icon dont-change-state' ? 'btn-blank btn-todo sidebar-collapsed-icon dont-change-state'
: 'btn btn-default btn-todo issuable-header-btn float-right'; : 'gl-button btn btn-default btn-todo issuable-header-btn float-right';
}, },
buttonLabel() { buttonLabel() {
return this.isTodo ? MARK_TEXT : TODO_TEXT; return this.isTodo ? MARK_TEXT : TODO_TEXT;
......
mutation mergeRequestRequestRereview($projectPath: ID!, $iid: String!, $userId: ID!) {
mergeRequestReviewerRereview(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
errors
}
}
import sidebarDetailsQuery from 'ee_else_ce/sidebar/queries/sidebarDetails.query.graphql'; import sidebarDetailsQuery from 'ee_else_ce/sidebar/queries/sidebarDetails.query.graphql';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql'; import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
export const gqClient = createGqClient( export const gqClient = createGqClient(
{}, {},
...@@ -70,4 +72,15 @@ export default class SidebarService { ...@@ -70,4 +72,15 @@ export default class SidebarService {
move_to_project_id: moveToProjectId, move_to_project_id: moveToProjectId,
}); });
} }
requestReview(userId) {
return gqClient.mutate({
mutation: reviewerRereviewMutation,
variables: {
userId: convertToGraphQLId('User', `${userId}`), // eslint-disable-line @gitlab/require-i18n-strings
projectPath: this.fullPath,
iid: this.iid.toString(),
},
});
}
} }
import Store from 'ee_else_ce/sidebar/stores/sidebar_store'; import Store from 'ee_else_ce/sidebar/stores/sidebar_store';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { visitUrl } from '../lib/utils/url_utility'; import { visitUrl } from '../lib/utils/url_utility';
import { deprecatedCreateFlash as Flash } from '../flash'; import { deprecatedCreateFlash as Flash } from '../flash';
...@@ -51,6 +52,17 @@ export default class SidebarMediator { ...@@ -51,6 +52,17 @@ export default class SidebarMediator {
return this.service.update(field, data); return this.service.update(field, data);
} }
requestReview({ userId, callback }) {
return this.service
.requestReview(userId)
.then(() => {
this.store.updateReviewer(userId);
toast(__('Requested review'));
callback(true);
})
.catch(() => callback(false));
}
setMoveToProjectId(projectId) { setMoveToProjectId(projectId) {
this.store.setMoveToProjectId(projectId); this.store.setMoveToProjectId(projectId);
} }
......
...@@ -96,6 +96,14 @@ export default class SidebarStore { ...@@ -96,6 +96,14 @@ export default class SidebarStore {
} }
} }
updateReviewer(id) {
const reviewer = this.findReviewer({ id });
if (reviewer) {
reviewer.reviewed = false;
}
}
findAssignee(findAssignee) { findAssignee(findAssignee) {
return this.assignees.find(({ id }) => id === findAssignee.id); return this.assignees.find(({ id }) => id === findAssignee.id);
} }
......
# frozen_string_literal: true
module CommentAndCloseFlag
extend ActiveSupport::Concern
included do
before_action do
push_frontend_feature_flag(:remove_comment_close_reopen, @group)
end
end
end
...@@ -9,6 +9,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -9,6 +9,7 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuesCalendar include IssuesCalendar
include SpammableActions include SpammableActions
include RecordUserLastActivity include RecordUserLastActivity
include CommentAndCloseFlag
ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze
SET_ISSUEABLES_INDEX_ONLY_ACTIONS = %i[index calendar service_desk].freeze SET_ISSUEABLES_INDEX_ONLY_ACTIONS = %i[index calendar service_desk].freeze
......
...@@ -11,6 +11,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -11,6 +11,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include RecordUserLastActivity include RecordUserLastActivity
include SourcegraphDecorator include SourcegraphDecorator
include DiffHelper include DiffHelper
include CommentAndCloseFlag
skip_before_action :merge_request, only: [:index, :bulk_update, :export_csv] skip_before_action :merge_request, only: [:index, :bulk_update, :export_csv]
before_action :apply_diff_view_cookie!, only: [:show] before_action :apply_diff_view_cookie!, only: [:show]
...@@ -22,7 +23,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -22,7 +23,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
:coverage_reports, :coverage_reports,
:terraform_reports, :terraform_reports,
:accessibility_reports, :accessibility_reports,
:codequality_reports :codequality_reports,
:codequality_mr_diff_reports
] ]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
...@@ -66,7 +68,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -66,7 +68,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
:toggle_award_emoji, :toggle_subscription, :update :toggle_award_emoji, :toggle_subscription, :update
] ]
feature_category :code_testing, [:test_reports, :coverage_reports] feature_category :code_testing, [:test_reports, :coverage_reports, :codequality_mr_diff_reports]
feature_category :accessibility_testing, [:accessibility_reports] feature_category :accessibility_testing, [:accessibility_reports]
feature_category :infrastructure_as_code, [:terraform_reports] feature_category :infrastructure_as_code, [:terraform_reports]
...@@ -195,6 +197,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -195,6 +197,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end end
end end
def codequality_mr_diff_reports
reports_response(@merge_request.find_codequality_mr_diff_reports)
end
def codequality_reports def codequality_reports
reports_response(@merge_request.compare_codequality_reports) reports_response(@merge_request.compare_codequality_reports)
end end
......
...@@ -49,7 +49,7 @@ module Ci ...@@ -49,7 +49,7 @@ module Ci
end end
def filter_by_scope(builds) def filter_by_scope(builds)
return filter_by_statuses!(params[:scope], builds) if params[:scope].is_a?(Array) return filter_by_statuses!(builds) if params[:scope].is_a?(Array)
case params[:scope] case params[:scope]
when 'pending' when 'pending'
...@@ -63,7 +63,7 @@ module Ci ...@@ -63,7 +63,7 @@ module Ci
end end
end end
def filter_by_statuses!(statuses, builds) def filter_by_statuses!(builds)
unknown_statuses = params[:scope] - ::CommitStatus::AVAILABLE_STATUSES unknown_statuses = params[:scope] - ::CommitStatus::AVAILABLE_STATUSES
raise ArgumentError, 'Scope contains invalid value(s)' unless unknown_statuses.empty? raise ArgumentError, 'Scope contains invalid value(s)' unless unknown_statuses.empty?
......
...@@ -8,7 +8,6 @@ module Ci ...@@ -8,7 +8,6 @@ module Ci
"project_path" => @project.full_path, "project_path" => @project.full_path,
"artifact_help_url" => help_page_path('user/gitlab_com/index.html', anchor: 'gitlab-cicd'), "artifact_help_url" => help_page_path('user/gitlab_com/index.html', anchor: 'gitlab-cicd'),
"deployment_help_url" => help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting'), "deployment_help_url" => help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting'),
"runner_help_url" => help_page_path('ci/runners/README.html', anchor: 'set-maximum-job-timeout-for-a-runner'),
"runner_settings_url" => project_runners_path(@build.project, anchor: 'js-runners-settings'), "runner_settings_url" => project_runners_path(@build.project, anchor: 'js-runners-settings'),
"variables_settings_url" => project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'), "variables_settings_url" => project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'),
"page_path" => project_job_path(@project, @build), "page_path" => project_job_path(@project, @build),
......
...@@ -203,6 +203,17 @@ module DiffHelper ...@@ -203,6 +203,17 @@ module DiffHelper
set_secure_cookie(:diff_view, params.delete(:view), type: CookiesHelper::COOKIE_TYPE_PERMANENT) if params[:view].present? set_secure_cookie(:diff_view, params.delete(:view), type: CookiesHelper::COOKIE_TYPE_PERMANENT) if params[:view].present?
end end
def collapsed_diff_url(diff_file)
url_for(
safe_params.merge(
action: :diff_for_path,
old_path: diff_file.old_path,
new_path: diff_file.new_path,
file_identifier: diff_file.file_identifier
)
)
end
private private
def diff_btn(title, name, selected) def diff_btn(title, name, selected)
......
...@@ -17,7 +17,7 @@ module OperationsHelper ...@@ -17,7 +17,7 @@ module OperationsHelper
'prometheus_authorization_key' => @project.alerting_setting&.token, 'prometheus_authorization_key' => @project.alerting_setting&.token,
'prometheus_api_url' => prometheus_service.api_url, 'prometheus_api_url' => prometheus_service.api_url,
'prometheus_url' => notify_project_prometheus_alerts_url(@project, format: :json), 'prometheus_url' => notify_project_prometheus_alerts_url(@project, format: :json),
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'), 'alerts_setup_url' => help_page_path('operations/incident_management/integrations.md', anchor: 'configuration'),
'alerts_usage_url' => project_alert_management_index_path(@project), 'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s, 'disabled' => disabled.to_s,
'project_path' => @project.full_path, 'project_path' => @project.full_path,
......
# frozen_string_literal: true # frozen_string_literal: true
# Backing store for GitLab session data.
#
# The raw session information is stored by the Rails session store
# (config/initializers/session_store.rb). These entries are accessible by the
# rack_key_name class method and consistute the base of the session data
# entries. All other entries in the session store can be traced back to these
# entries.
#
# After a user logs in (config/initializers/warden.rb) a further entry is made
# in Redis. This entry holds a record of the user's logged in session. These
# are accessible with the key_name(user_id, session_id) class method. These
# entries will expire. Lookups to these entries are lazilly cleaned on future
# user access.
#
# There is a reference to all sessions that belong to a specific user. A
# user may login through multiple browsers/devices and thus record multiple
# login sessions. These are accessible through the lookup_key_name(user_id)
# class method.
#
class ActiveSession class ActiveSession
include ActiveModel::Model include ActiveModel::Model
...@@ -143,6 +162,10 @@ class ActiveSession ...@@ -143,6 +162,10 @@ class ActiveSession
list(user).reject(&:is_impersonated) list(user).reject(&:is_impersonated)
end end
def self.rack_key_name(session_id)
"#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}"
end
def self.key_name(user_id, session_id = '*') def self.key_name(user_id, session_id = '*')
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}" "#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
end end
...@@ -197,7 +220,7 @@ class ActiveSession ...@@ -197,7 +220,7 @@ class ActiveSession
end end
def self.rack_session_keys(rack_session_ids) def self.rack_session_keys(rack_session_ids)
rack_session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" } rack_session_ids.map { |session_id| rack_key_name(session_id)}
end end
def self.raw_active_session_entries(redis, session_ids, user_id) def self.raw_active_session_entries(redis, session_ids, user_id)
......
...@@ -44,6 +44,8 @@ class ApplicationSetting < ApplicationRecord ...@@ -44,6 +44,8 @@ class ApplicationSetting < ApplicationRecord
serialize :domain_denylist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_denylist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
serialize :asset_proxy_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :asset_proxy_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
# See https://gitlab.com/gitlab-org/gitlab/-/issues/300916
serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
cache_markdown_field :sign_in_text cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text cache_markdown_field :help_page_text
......
...@@ -76,6 +76,22 @@ module Ci ...@@ -76,6 +76,22 @@ module Ci
end end
end end
##
# Sometime we need to ensure that the first read goes to a primary
# database, what is especially important in EE. This method does not
# change the behavior in CE.
#
def with_read_consistency(build, &block)
return yield unless consistent_reads_enabled?(build)
::Gitlab::Database::Consistency
.with_read_consistency(&block)
end
def consistent_reads_enabled?(build)
Feature.enabled?(:gitlab_ci_trace_read_consistency, build.project, type: :development, default_enabled: false)
end
## ##
# Sometimes we do not want to read raw data. This method makes it easier # Sometimes we do not want to read raw data. This method makes it easier
# to find attributes that are just metadata excluding raw data. # to find attributes that are just metadata excluding raw data.
...@@ -154,8 +170,8 @@ module Ci ...@@ -154,8 +170,8 @@ module Ci
in_lock(lock_key, **lock_params) do # exclusive Redis lock is acquired first in_lock(lock_key, **lock_params) do # exclusive Redis lock is acquired first
raise FailedToPersistDataError, 'Modifed build trace chunk detected' if has_changes_to_save? raise FailedToPersistDataError, 'Modifed build trace chunk detected' if has_changes_to_save?
self.reset.then do |chunk| # we ensure having latest lock_version self.class.with_read_consistency(build) do
chunk.unsafe_persist_data! # we migrate the data and update data store self.reset.then { |chunk| chunk.unsafe_persist_data! }
end end
end end
rescue FailedToObtainLockError rescue FailedToObtainLockError
......
...@@ -1003,8 +1003,8 @@ module Ci ...@@ -1003,8 +1003,8 @@ module Ci
has_reports?(Ci::JobArtifact.coverage_reports) has_reports?(Ci::JobArtifact.coverage_reports)
end end
def has_codequality_reports? def has_codequality_mr_diff_report?
pipeline_artifacts&.has_report?(:code_quality) pipeline_artifacts&.has_report?(:code_quality_mr_diff)
end end
def can_generate_codequality_reports? def can_generate_codequality_reports?
......
...@@ -15,7 +15,7 @@ module Ci ...@@ -15,7 +15,7 @@ module Ci
DEFAULT_FILE_NAMES = { DEFAULT_FILE_NAMES = {
code_coverage: 'code_coverage.json', code_coverage: 'code_coverage.json',
code_quality: 'code_quality.json' code_quality_mr_diff: 'code_quality_mr_diff.json'
}.freeze }.freeze
belongs_to :project, class_name: "Project", inverse_of: :pipeline_artifacts belongs_to :project, class_name: "Project", inverse_of: :pipeline_artifacts
...@@ -32,7 +32,7 @@ module Ci ...@@ -32,7 +32,7 @@ module Ci
enum file_type: { enum file_type: {
code_coverage: 1, code_coverage: 1,
code_quality: 2 code_quality_mr_diff: 2
} }
class << self class << self
......
...@@ -1484,6 +1484,24 @@ class MergeRequest < ApplicationRecord ...@@ -1484,6 +1484,24 @@ class MergeRequest < ApplicationRecord
compare_reports(Ci::GenerateCoverageReportsService) compare_reports(Ci::GenerateCoverageReportsService)
end end
def has_codequality_mr_diff_report?
return false unless ::Gitlab::Ci::Features.display_quality_on_mr_diff?(project)
actual_head_pipeline&.has_codequality_mr_diff_report?
end
# TODO: this method and compare_test_reports use the same
# result type, which is handled by the controller's #reports_response.
# we should minimize mistakes by isolating the common parts.
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
def find_codequality_mr_diff_reports
unless has_codequality_mr_diff_report?
return { status: :error, status_reason: 'This merge request does not have codequality mr diff reports' }
end
compare_reports(Ci::GenerateCodequalityMrDiffReportService)
end
def has_codequality_reports? def has_codequality_reports?
return false unless ::Gitlab::Ci::Features.display_quality_on_mr_diff?(project) return false unless ::Gitlab::Ci::Features.display_quality_on_mr_diff?(project)
......
...@@ -30,8 +30,8 @@ class ConfluenceService < Service ...@@ -30,8 +30,8 @@ class ConfluenceService < Service
s_('ConfluenceService|Connect a Confluence Cloud Workspace to GitLab') s_('ConfluenceService|Connect a Confluence Cloud Workspace to GitLab')
end end
def detailed_description def help
return unless project.wiki_enabled? return unless project&.wiki_enabled?
if activated? if activated?
wiki_url = project.wiki.web_url wiki_url = project.wiki.web_url
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Ci module Ci
module PipelineArtifacts module PipelineArtifacts
class CodeCoveragePresenter < ProcessablePresenter class CodeCoveragePresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
def for_files(filenames) def for_files(filenames)
......
# frozen_string_literal: true
module Ci
module PipelineArtifacts
class CodeQualityMrDiffPresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize
def for_files(filenames)
quality_files = raw_report["files"].select { |key| filenames.include?(key) }
{ files: quality_files }
end
private
def raw_report
strong_memoize(:raw_report) do
self.each_blob do |blob|
Gitlab::Json.parse(blob).with_indifferent_access
end
end
end
end
end
end
# frozen_string_literal: true
module Ci
# TODO: a couple of points with this approach:
# + reuses existing architecture and reactive caching
# - it's not a report comparison and some comparing features must be turned off.
# see CompareReportsBaseService for more notes.
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
class GenerateCodequalityMrDiffReportService < CompareReportsBaseService
def execute(base_pipeline, head_pipeline)
merge_request = MergeRequest.find_by_id(params[:id])
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
data: head_pipeline.pipeline_artifacts.find_by_file_type(:code_quality_mr_diff).present.for_files(merge_request.new_paths)
}
rescue => e
Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
{
status: :error,
key: key(base_pipeline, head_pipeline),
status_reason: _('An error occurred while fetching codequality mr diff reports.')
}
end
def latest?(base_pipeline, head_pipeline, data)
data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
module Ci module Ci
module PipelineArtifacts module PipelineArtifacts
class CreateQualityReportService class CreateCodeQualityMrDiffReportService
def execute(pipeline) def execute(pipeline)
return unless pipeline.can_generate_codequality_reports? return unless pipeline.can_generate_codequality_reports?
return if pipeline.has_codequality_reports? return if pipeline.has_codequality_mr_diff_report?
file = build_carrierwave_file(pipeline) file = build_carrierwave_file(pipeline)
pipeline.pipeline_artifacts.create!( pipeline.pipeline_artifacts.create!(
project_id: pipeline.project_id, project_id: pipeline.project_id,
file_type: :code_quality, file_type: :code_quality_mr_diff,
file_format: :raw, file_format: :raw,
size: file["tempfile"].size, size: file["tempfile"].size,
file: file, file: file,
...@@ -23,7 +23,7 @@ module Ci ...@@ -23,7 +23,7 @@ module Ci
def build_carrierwave_file(pipeline) def build_carrierwave_file(pipeline)
CarrierWaveStringFile.new_file( CarrierWaveStringFile.new_file(
file_content: build_quality_mr_diff_report(pipeline), file_content: build_quality_mr_diff_report(pipeline),
filename: Ci::PipelineArtifact::DEFAULT_FILE_NAMES.fetch(:code_quality), filename: Ci::PipelineArtifact::DEFAULT_FILE_NAMES.fetch(:code_quality_mr_diff),
content_type: 'application/json' content_type: 'application/json'
) )
end end
......
# frozen_string_literal: true # frozen_string_literal: true
module UpdateRepositoryStorageMethods module UpdateRepositoryStorageMethods
include Gitlab::Utils::StrongMemoize
Error = Class.new(StandardError) Error = Class.new(StandardError)
SameFilesystemError = Class.new(Error)
attr_reader :repository_storage_move attr_reader :repository_storage_move
delegate :container, :source_storage_name, :destination_storage_name, to: :repository_storage_move delegate :container, :source_storage_name, :destination_storage_name, to: :repository_storage_move
...@@ -18,9 +19,7 @@ module UpdateRepositoryStorageMethods ...@@ -18,9 +19,7 @@ module UpdateRepositoryStorageMethods
repository_storage_move.start! repository_storage_move.start!
end end
raise SameFilesystemError if same_filesystem?(source_storage_name, destination_storage_name) mirror_repositories unless same_filesystem?
mirror_repositories
repository_storage_move.transaction do repository_storage_move.transaction do
repository_storage_move.finish_replication! repository_storage_move.finish_replication!
...@@ -28,8 +27,10 @@ module UpdateRepositoryStorageMethods ...@@ -28,8 +27,10 @@ module UpdateRepositoryStorageMethods
track_repository(destination_storage_name) track_repository(destination_storage_name)
end end
remove_old_paths unless same_filesystem?
enqueue_housekeeping remove_old_paths
enqueue_housekeeping
end
repository_storage_move.finish_cleanup! repository_storage_move.finish_cleanup!
...@@ -80,8 +81,10 @@ module UpdateRepositoryStorageMethods ...@@ -80,8 +81,10 @@ module UpdateRepositoryStorageMethods
end end
end end
def same_filesystem?(old_storage, new_storage) def same_filesystem?
Gitlab::GitalyClient.filesystem_id(old_storage) == Gitlab::GitalyClient.filesystem_id(new_storage) strong_memoize(:same_filesystem) do
Gitlab::GitalyClient.filesystem_id(source_storage_name) == Gitlab::GitalyClient.filesystem_id(destination_storage_name)
end
end end
def remove_old_paths def remove_old_paths
......
...@@ -126,3 +126,5 @@ module Groups ...@@ -126,3 +126,5 @@ module Groups
end end
end end
end end
Groups::ImportExport::ExportService.prepend_if_ee('EE::Groups::ImportExport::ExportService')
...@@ -123,3 +123,5 @@ module Groups ...@@ -123,3 +123,5 @@ module Groups
end end
end end
end end
Groups::ImportExport::ImportService.prepend_if_ee('EE::Groups::ImportExport::ImportService')
...@@ -24,9 +24,7 @@ module Pages ...@@ -24,9 +24,7 @@ module Pages
@queue.close @queue.close
@logger.info("Waiting for threads to finish...") @logger.info("Waiting for threads to finish...")
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do threads.each(&:join)
threads.each(&:join)
end
{ migrated: @migrated, errored: @errored } { migrated: @migrated, errored: @errored }
end end
...@@ -34,8 +32,8 @@ module Pages ...@@ -34,8 +32,8 @@ module Pages
def start_migration_threads def start_migration_threads
Array.new(@migration_threads) do Array.new(@migration_threads) do
Thread.new do Thread.new do
Rails.application.executor.wrap do while batch = @queue.pop
while batch = @queue.pop Rails.application.executor.wrap do
process_batch(batch) process_batch(batch)
end end
end end
...@@ -51,6 +49,11 @@ module Pages ...@@ -51,6 +49,11 @@ module Pages
end end
@logger.info("#{@migrated} projects are migrated successfully, #{@errored} projects failed to be migrated") @logger.info("#{@migrated} projects are migrated successfully, #{@errored} projects failed to be migrated")
rescue => e
# This method should never raise exception otherwise all threads might be killed
# and this will result in queue starving (and deadlock)
Gitlab::ErrorTracking.track_exception(e)
@logger.error("failed processing a batch: #{e.message}")
end end
def migrate_project(project) def migrate_project(project)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.svg-content.svg-250 .svg-content.svg-250
= image_tag 'illustrations/starred_empty.svg' = image_tag 'illustrations/starred_empty.svg'
.text-content .text-content
%h4.text-center %h4.gl-text-center
= s_("StarredProjectsEmptyState|You don't have starred projects yet.") = s_("StarredProjectsEmptyState|You don't have starred projects yet.")
%p.text-secondary %p.gl-text-gray-500
= s_("StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page.") = s_("StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page.")
- diff_file = viewer.diff_file .nothing-here-block.diff-collapsed{ data: { diff_for_path: collapsed_diff_url(viewer.diff_file) } }
- url = url_for(safe_params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
.nothing-here-block.diff-collapsed{ data: { diff_for_path: url } }
= _("This diff is collapsed.") = _("This diff is collapsed.")
%button.click-to-expand.btn.btn-link= _("Click to expand it.") %button.click-to-expand.btn.btn-link= _("Click to expand it.")
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
.sub-header-block.bg-gray-light.gl-p-5 .sub-header-block.bg-gray-light.gl-p-5
.tree-ref-holder.inline.vertical-align-middle .tree-ref-holder.inline.vertical-align-middle
= render 'shared/ref_switcher', destination: 'graphs' = render 'shared/ref_switcher', destination: 'graphs'
= link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn gl-button' = link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn gl-button btn-default'
.js-contributors-graph{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json),'data-project-branch': current_ref } .js-contributors-graph{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json),'data-project-branch': current_ref }
- if lookup_context.template_exists?('top', "projects/services/#{@service.to_param}", true) - if lookup_context.template_exists?('top', "projects/services/#{@service.to_param}", true)
= render "projects/services/#{@service.to_param}/top" = render "projects/services/#{@service.to_param}/top"
.row.gl-mt-3.gl-mb-3 %h3.page-title
.col-lg-4 = @service.title
%h3.page-title.gl-mt-0 - if @service.operating?
= @service.title = sprite_icon('check', css_class: 'gl-text-green-500')
- if @service.operating?
= sprite_icon('check', css_class: 'gl-text-green-500')
- if @service.respond_to?(:detailed_description) = form_for(@service, as: :service, url: scoped_integration_path(@service), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => test_project_service_path(@project, @service) } }) do |form|
%p= @service.detailed_description = render 'shared/service_settings', form: form, integration: @service
.col-lg-8 %input{ id: 'services_redirect_to', type: 'hidden', name: 'redirect_to', value: request.referrer }
= form_for(@service, as: :service, url: scoped_integration_path(@service), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, integration: @service
%input{ id: 'services_redirect_to', type: 'hidden', name: 'redirect_to', value: request.referrer }
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true) - if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr %hr
......
...@@ -15,5 +15,5 @@ ...@@ -15,5 +15,5 @@
= render 'shared/notes/hints' = render 'shared/notes/hints'
.error-alert .error-alert
.gl-mt-3 .gl-mt-3
= f.submit 'Save changes', class: 'btn btn-success' = f.submit 'Save changes', class: 'btn gl-button btn-success'
= link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel" = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn gl-button btn-default btn-cancel"
...@@ -42,13 +42,13 @@ ...@@ -42,13 +42,13 @@
- if @tag.has_signature? - if @tag.has_signature?
= render partial: 'projects/commit/signature', object: @tag.signature = render partial: 'projects/commit/signature', object: @tag.signature
- if can?(current_user, :admin_tag, @project) - if can?(current_user, :admin_tag, @project)
= link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-icon btn-edit gl-button controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-icon btn-edit gl-button btn-default controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
= sprite_icon("pencil", css_class: 'gl-icon') = sprite_icon("pencil", css_class: 'gl-icon')
= link_to project_tree_path(@project, @tag.name), class: 'btn btn-icon gl-button controls-item has-tooltip', title: s_('TagsPage|Browse files') do = link_to project_tree_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default controls-item has-tooltip', title: s_('TagsPage|Browse files') do
= sprite_icon('folder-open', css_class: 'gl-icon') = sprite_icon('folder-open', css_class: 'gl-icon')
= link_to project_commits_path(@project, @tag.name), class: 'btn btn-icon gl-button controls-item has-tooltip', title: s_('TagsPage|Browse commits') do = link_to project_commits_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default controls-item has-tooltip', title: s_('TagsPage|Browse commits') do
= sprite_icon('history', css_class: 'gl-icon') = sprite_icon('history', css_class: 'gl-icon')
.btn-container.controls-item .controls-item
= render 'projects/buttons/download', project: @project, ref: @tag.name = render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_tag, @project) - if can?(current_user, :admin_tag, @project)
.btn-container.controls-item-full .btn-container.controls-item-full
......
...@@ -43,10 +43,10 @@ ...@@ -43,10 +43,10 @@
- if current_user - if current_user
%li.inline.label-subscription %li.inline.label-subscription
- if label.can_subscribe_to_label_in_different_levels? - if label.can_subscribe_to_label_in_different_levels?
%button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } %button.js-unsubscribe-button.gl-button.label-subscribe-button.btn.btn-default.gl-ml-3{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span= _('Unsubscribe') %span= _('Unsubscribe')
.dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) }
%button.gl-button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } } %button.gl-button.label-subscribe-button.btn.btn-default.gl-ml-3{ data: { toggle: 'dropdown' } }
%span %span
= _('Subscribe') = _('Subscribe')
= sprite_icon('chevron-down') = sprite_icon('chevron-down')
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
%button.js-subscribe-button.js-group-level.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } %button.js-subscribe-button.js-group-level.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } }
%span= _('Subscribe at group level') %span= _('Subscribe at group level')
- else - else
%button.gl-button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } %button.gl-button.js-subscribe-button.label-subscribe-button.btn.btn-default.gl-ml-3{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span= label_subscription_toggle_button_text(label, @project) %span= label_subscription_toggle_button_text(label, @project)
= render 'shared/delete_label_modal', label: label = render 'shared/delete_label_modal', label: label
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- if @default_integration - if @default_integration
.js-vue-default-integration-settings{ data: integration_form_data(@default_integration, group: @group) } .js-vue-default-integration-settings{ data: integration_form_data(@default_integration, group: @group) }
.js-vue-integration-settings{ data: integration_form_data(integration, group: @group) } .js-vue-integration-settings{ data: integration_form_data(integration, group: @group) }
.js-integration-help-html .js-integration-help-html.gl-display-none
-# All content below will be repositioned in Vue -# All content below will be repositioned in Vue
- if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true) - if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true)
= render "projects/services/#{integration.to_param}/help", subject: integration = render "projects/services/#{integration.to_param}/help", subject: integration
......
- integration = local_assigns.fetch(:integration) - integration = local_assigns.fetch(:integration)
.row.gl-mt-3 %h3.page-title
.col-lg-4 = integration.title
%h3.page-title.gl-mt-0
= integration.title
.col-lg-8 = form_for integration, as: :service, url: scoped_integration_path(integration), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => scoped_test_integration_path(integration) } } do |form|
= form_for integration, as: :service, url: scoped_integration_path(integration), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => scoped_test_integration_path(integration) } } do |form| = render 'shared/service_settings', form: form, integration: integration
= render 'shared/service_settings', form: form, integration: integration
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- button_icon = has_todo ? todo_button_data[:mark_icon] : todo_button_data[:todo_icon] - button_icon = has_todo ? todo_button_data[:mark_icon] : todo_button_data[:todo_icon]
%button.issuable-todo-btn.js-issuable-todo{ type: 'button', %button.issuable-todo-btn.js-issuable-todo{ type: 'button',
class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn float-right'), class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'gl-button btn btn-default issuable-header-btn float-right'),
title: button_title, title: button_title,
'aria-label' => button_title, 'aria-label' => button_title,
data: todo_button_data } data: todo_button_data }
......
...@@ -12,7 +12,7 @@ module Ci ...@@ -12,7 +12,7 @@ module Ci
def perform(pipeline_id) def perform(pipeline_id)
Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline| Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline|
Ci::PipelineArtifacts::CreateQualityReportService.new.execute(pipeline) Ci::PipelineArtifacts::CreateCodeQualityMrDiffReportService.new.execute(pipeline)
end end
end end
end end
......
---
title: 'Editor Lite: support for Diff Instance'
merge_request: 51470
author:
type: added
---
title: Add btn-default class for history button in the contributors page
merge_request: 52861
author: Yogi (@yo)
type: other
---
title: Lower allocations in _collapsed partial
merge_request: 53233
author:
type: performance
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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