summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-23 18:08:31 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-23 18:08:31 +0000
commit7e1e5ca371cbfe77bdf56fcf65a4e749e6e86a06 (patch)
treec40953fc74404b5d86bb2ef72bab89526c5ca7d6
parent9086e66ee72527839053ec6db19ed321a3b3a61b (diff)
downloadgitlab-ce-7e1e5ca371cbfe77bdf56fcf65a4e749e6e86a06.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/cng.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml14
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml5
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml4
-rw-r--r--.gitlab/merge_request_templates/Documentation.md8
-rw-r--r--.rubocop_todo.yml1
-rw-r--r--.ruby-version2
-rw-r--r--README.md2
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss2
-rw-r--r--app/assets/stylesheets/components/severity/icons.scss (renamed from app/assets/stylesheets/pages/alert_management/severity-icons.scss)12
-rw-r--r--app/assets/stylesheets/fontawesome_custom.scss56
-rw-r--r--app/assets/stylesheets/page_bundles/alert_management_details.scss (renamed from app/assets/stylesheets/pages/alert_management/details.scss)16
-rw-r--r--app/controllers/concerns/routable_actions.rb2
-rw-r--r--app/graphql/types/terraform/state_type.rb2
-rw-r--r--app/models/ci/test_case.rb35
-rw-r--r--app/models/ci/test_case_failure.rb12
-rw-r--r--app/models/service.rb11
-rw-r--r--app/services/admin/propagate_integration_service.rb45
-rw-r--r--app/services/bulk_create_integration_service.rb2
-rw-r--r--app/services/bulk_update_integration_service.rb2
-rw-r--r--app/services/ci/test_cases_service.rb44
-rw-r--r--app/services/concerns/admin/propagate_service.rb13
-rw-r--r--app/views/admin/dev_ops_report/_report.html.haml20
-rw-r--r--app/views/admin/dev_ops_report/show.html.haml21
-rw-r--r--app/views/projects/alert_management/details.html.haml1
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/build_finished_worker.rb5
-rw-r--r--app/workers/propagate_integration_inherit_descendant_worker.rb19
-rw-r--r--app/workers/propagate_integration_inherit_worker.rb4
-rw-r--r--changelogs/unreleased/259024-labels-api-search-support.yml5
-rw-r--r--changelogs/unreleased/267147-graphql-total-count.yml5
-rw-r--r--changelogs/unreleased/eb-test-failure-history-mvc.yml5
-rw-r--r--changelogs/unreleased/fj-change-routable-redirect-to-301.yml5
-rw-r--r--changelogs/unreleased/sh-upgrade-ruby-2-7-2-ci.yml5
-rw-r--r--changelogs/unreleased/vij-add-snippet-repo-readonly.yml5
-rw-r--r--config/application.rb1
-rw-r--r--config/feature_flags/development/test_failure_history.yml7
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/migrate/20201012134230_create_ci_test_cases.rb27
-rw-r--r--db/migrate/20201012135330_create_ci_test_case_failures.rb22
-rw-r--r--db/migrate/20201012140110_add_projects_fk_to_ci_test_cases.rb19
-rw-r--r--db/migrate/20201012140452_add_ci_builds_fk_to_ci_test_case_failures.rb19
-rw-r--r--db/migrate/20201022080802_add_repository_read_only_to_snippets.rb9
-rw-r--r--db/schema_migrations/202010121342301
-rw-r--r--db/schema_migrations/202010121353301
-rw-r--r--db/schema_migrations/202010121401101
-rw-r--r--db/schema_migrations/202010121404521
-rw-r--r--db/schema_migrations/202010220808021
-rw-r--r--db/structure.sql60
-rw-r--r--doc/administration/audit_events.md8
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql22
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json65
-rw-r--r--doc/api/group_labels.md5
-rw-r--r--doc/api/labels.md1
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/operations/metrics/dashboards/variables.md2
-rw-r--r--doc/user/compliance/license_compliance/index.md6
-rw-r--r--doc/user/group/subgroups/index.md5
-rw-r--r--lib/api/group_labels.rb14
-rw-r--r--lib/api/helpers.rb11
-rw-r--r--lib/api/helpers/label_helpers.rb12
-rw-r--r--lib/api/labels.rb6
-rw-r--r--lib/gitlab/import_export/project/import_export.yml1
-rw-r--r--locale/gitlab.pot33
-rw-r--r--qa/Dockerfile4
-rw-r--r--qa/qa/resource/runner.rb2
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb2
-rw-r--r--scripts/prepare_build.sh2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb6
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb4
-rw-r--r--spec/controllers/projects/registry/repositories_controller_spec.rb4
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb6
-rw-r--r--spec/factories/audit_events.rb6
-rw-r--r--spec/factories/ci/builds.rb6
-rw-r--r--spec/factories/ci/job_artifacts.rb10
-rw-r--r--spec/factories/ci/reports/test_case.rb41
-rw-r--r--spec/factories/ci/test_case.rb39
-rw-r--r--spec/factories/ci/test_case_failure.rb9
-rw-r--r--spec/features/profile_spec.rb3
-rw-r--r--spec/fixtures/junit/junit_with_duplicate_failed_test_names.xml.gzbin0 -> 576 bytes
-rw-r--r--spec/graphql/resolvers/release_resolver_spec.rb2
-rw-r--r--spec/lib/api/helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_case_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb4
-rw-r--r--spec/lib/gitlab/config/entry/simplifiable_spec.rb6
-rw-r--r--spec/lib/gitlab/tracking/incident_management_spec.rb2
-rw-r--r--spec/models/ci/test_case_failure_spec.rb18
-rw-r--r--spec/models/ci/test_case_spec.rb31
-rw-r--r--spec/models/concerns/optionally_search_spec.rb2
-rw-r--r--spec/models/member_spec.rb11
-rw-r--r--spec/models/service_spec.rb17
-rw-r--r--spec/requests/api/graphql/project/terraform/states_spec.rb59
-rw-r--r--spec/requests/api/group_labels_spec.rb129
-rw-r--r--spec/requests/api/labels_spec.rb60
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb4
-rw-r--r--spec/serializers/test_case_entity_spec.rb8
-rw-r--r--spec/services/admin/propagate_integration_service_spec.rb14
-rw-r--r--spec/services/bulk_create_integration_service_spec.rb23
-rw-r--r--spec/services/bulk_update_integration_service_spec.rb33
-rw-r--r--spec/services/ci/test_cases_service_spec.rb94
-rw-r--r--spec/support/helpers/table_schema_helpers.rb4
-rw-r--r--spec/support/shared_examples/controllers/trackable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/labels_api_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/tracking_shared_examples.rb2
-rw-r--r--spec/workers/build_finished_worker_spec.rb42
-rw-r--r--spec/workers/propagate_integration_inherit_descendant_worker_spec.rb30
-rw-r--r--spec/workers/propagate_integration_inherit_worker_spec.rb2
115 files changed, 1198 insertions, 365 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f9c9c5888db..8a111b1f695 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -17,7 +17,7 @@ stages:
# in cases where jobs require Docker-in-Docker, the job
# definition must be extended with `.use-docker-in-docker`
default:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
tags:
- gitlab-org
# All jobs are interruptible by default
diff --git a/.gitlab/ci/cng.gitlab-ci.yml b/.gitlab/ci/cng.gitlab-ci.yml
index d7699de74e2..269996dfd09 100644
--- a/.gitlab/ci/cng.gitlab-ci.yml
+++ b/.gitlab/ci/cng.gitlab-ci.yml
@@ -1,6 +1,6 @@
cloud-native-image:
extends: .cng:rules
- image: ruby:2.6-alpine
+ image: ruby:2.7-alpine
dependencies: []
stage: post-test
variables:
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 4b25908aa6a..279bc462d71 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -2,7 +2,7 @@
extends:
- .default-retry
- .docs:rules:review-docs
- image: ruby:2.6-alpine
+ image: ruby:2.7-alpine
stage: review
needs: []
variables:
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index e4c9f85cf62..1b78996282c 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -15,7 +15,7 @@
extends:
- .frontend-base
- .assets-compile-cache
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.28-lfs-2.9-node-12.18-yarn-1.22-graphicsmagick-1.3.34
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.28-lfs-2.9-node-12.18-yarn-1.22-graphicsmagick-1.3.34
variables:
WEBPACK_VENDOR_DLL: "true"
stage: prepare
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index fea3956bfe8..3ff17ce05d2 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -18,7 +18,7 @@
.rails-cache:
cache:
- key: "rails-v2"
+ key: "rails-v3"
paths:
- vendor/ruby/
- vendor/gitaly-ruby/
@@ -27,7 +27,7 @@
.static-analysis-cache:
cache:
- key: "static-analysis-v1"
+ key: "static-analysis-v2"
paths:
- vendor/ruby/
- node_modules/
@@ -43,7 +43,7 @@
.qa-cache:
cache:
- key: "qa-v1"
+ key: "qa-v2"
paths:
- qa/vendor/ruby/
policy: pull
@@ -71,7 +71,7 @@
policy: pull
.use-pg11:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -80,7 +80,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
services:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -89,7 +89,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -100,7 +100,7 @@
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg12-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34"
services:
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 96a8f093fea..1a1ead4054a 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -7,7 +7,8 @@
before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- cd qa/
- - bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet
+ - gem install bundler -v 1.17.3
+ - bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --without=development --quiet
- bundle check
qa:internal:
@@ -47,7 +48,7 @@ update-qa-cache:
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
.package-and-qa-base:
- image: ruby:2.6-alpine
+ image: ruby:2.7-alpine
stage: qa
retry: 0
script:
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index c4167ce7bcb..70fd7d7d85b 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -1,4 +1,4 @@
-######################
+#######################
# rspec job base specs
.rails-job-base:
extends:
@@ -181,6 +181,7 @@ update-coverage-cache:
- .shared:rules:update-cache
stage: prepare
script:
+ - run_timed_command "gem install bundler -v 1.17.3"
- run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --without default development test production puma unicorn kerberos metrics omnibus ed25519"
cache:
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
@@ -358,6 +359,7 @@ rspec:coverage:
- memory-static
- memory-on-boot
script:
+ - run_timed_command "gem install bundler -v 1.17.3"
- 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/merge-simplecov"
- run_timed_command "bundle exec scripts/gather-test-memory-data"
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 46a1a957692..d3069657e88 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -25,7 +25,7 @@ review-build-cng:
extends:
- .default-retry
- .review:rules:review-build-cng
- image: ruby:2.6-alpine
+ image: ruby:2.7-alpine
stage: review-prepare
before_script:
- source ./scripts/utils.sh
@@ -122,7 +122,7 @@ review-stop:
extends:
- .default-retry
- .use-docker-in-docker
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7
stage: qa
# This is needed so that manual jobs with needs don't block the pipeline.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
@@ -199,7 +199,7 @@ review-performance:
parallel-spec-reports:
extends:
- .review:rules:mr-only-manual
- image: ruby:2.6-alpine
+ image: ruby:2.7-alpine
stage: post-qa
dependencies: ["review-qa-all"]
variables:
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index cf42d2a8a5e..abe7625c740 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -52,7 +52,7 @@ no_ee_check:
verify-tests-yml:
extends:
- .setup:rules:verify-tests-yml
- image: ruby:2.6-alpine
+ image: ruby:2.7-alpine
stage: test
needs: []
script:
@@ -61,7 +61,7 @@ verify-tests-yml:
- scripts/verify-tff-mapping
.detect-test-base:
- image: ruby:2.6-alpine
+ image: ruby:2.7-alpine
needs: []
stage: prepare
script:
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index 8713405033b..b059c1f68ad 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -55,6 +55,14 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
For more information about labels, see [Technical Writing workflows - Labels](https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#labels).
+For suggestions that you are confident don't need to be reviewed, change them locally
+and push a commit directly to save others from unneeded reviews. For example:
+
+- Clear typos, like `this is a typpo`.
+- Minor issues, like single quotes instead of double quotes, Oxford commas, and periods.
+
+For more information, see our documentation on [Merging a merge request](https://docs.gitlab.com/ee/development/code_review.html#merging-a-merge-request).
+
**3. Maintainer**
1. [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review.
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 582c66bdffd..ba31ea5704e 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1269,7 +1269,6 @@ FactoryBot/InlineAssociation:
- 'ee/spec/factories/resource_weight_events.rb'
- 'ee/spec/factories/vulnerabilities/feedback.rb'
- 'spec/factories/atlassian_identities.rb'
- - 'spec/factories/audit_events.rb'
- 'spec/factories/design_management/design_at_version.rb'
- 'spec/factories/design_management/designs.rb'
- 'spec/factories/design_management/versions.rb'
diff --git a/.ruby-version b/.ruby-version
index 338a5b5d8fe..37c2961c243 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.6.6
+2.7.2
diff --git a/README.md b/README.md
index 03c2a709bee..e3712d3f9f8 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL/OpenSUSE
-- Ruby (MRI) 2.6.6
+- Ruby (MRI) 2.7.2
- Git 2.24+
- Redis 4.0+
- PostgreSQL 11+
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index ad4d42abae6..f3bf2dfc61c 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -1,6 +1,4 @@
@import './pages/admin';
-@import './pages/alert_management/details';
-@import './pages/alert_management/severity-icons';
@import './pages/branches';
@import './pages/builds';
@import './pages/ci_projects';
diff --git a/app/assets/stylesheets/pages/alert_management/severity-icons.scss b/app/assets/stylesheets/components/severity/icons.scss
index f58ad87a673..8ddf873196a 100644
--- a/app/assets/stylesheets/pages/alert_management/severity-icons.scss
+++ b/app/assets/stylesheets/components/severity/icons.scss
@@ -2,26 +2,26 @@
.incident-management-list,
.alert-management-details {
.icon-critical {
- color: $red-800;
+ @include gl-text-red-800;
}
.icon-high {
- color: $red-600;
+ @include gl-text-red-600;
}
.icon-medium {
- color: $orange-400;
+ @include gl-text-orange-400;
}
.icon-low {
- color: $orange-300;
+ @include gl-text-orange-300;
}
.icon-info {
- color: $blue-400;
+ @include gl-text-blue-400;
}
.icon-unknown {
- color: $gray-200;
+ @include gl-text-gray-200;
}
}
diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss
index 937ed5119cb..4abe2f9f009 100644
--- a/app/assets/stylesheets/fontawesome_custom.scss
+++ b/app/assets/stylesheets/fontawesome_custom.scss
@@ -105,10 +105,6 @@
content: '\f110';
}
-.fa-trash-o::before {
- content: '\f014';
-}
-
.fa-caret-right::before {
content: '\f0da';
}
@@ -117,18 +113,10 @@
content: '\f077';
}
-.fa-bug::before {
- content: '\f188';
-}
-
.fa-exclamation-circle::before {
content: '\f06a';
}
-.fa-bell::before {
- content: '\f0f3';
-}
-
.fa-file-o::before {
content: '\f016';
}
@@ -141,10 +129,6 @@
content: '\f111';
}
-.fa-git::before {
- content: '\f1d3';
-}
-
.fa-thumb-tack::before {
content: '\f08d';
}
@@ -153,38 +137,6 @@
content: '\f06d';
}
-.fa-pause::before {
- content: '\f04c';
-}
-
-.fa-play::before {
- content: '\f04b';
-}
-
-.fa-share::before {
- content: '\f064';
-}
-
-.fa-book::before {
- content: '\f02d';
-}
-
-.fa-times-circle::before {
- content: '\f057';
-}
-
-.fa-skype::before {
- content: '\f17e';
-}
-
-.fa-linkedin-square::before {
- content: '\f08c';
-}
-
-.fa-twitter-square::before {
- content: '\f081';
-}
-
.fa-file-pdf-o::before {
content: '\f1c1';
}
@@ -217,6 +169,14 @@
content: '\f1c8';
}
+.fa-square-o::before {
+ content: '\f096';
+}
+
+.fa-check-square-o::before {
+ content: '\f046';
+}
+
.sr-only {
position: absolute;
width: 1px;
diff --git a/app/assets/stylesheets/pages/alert_management/details.scss b/app/assets/stylesheets/page_bundles/alert_management_details.scss
index 514f228e223..beb80a14c5a 100644
--- a/app/assets/stylesheets/pages/alert_management/details.scss
+++ b/app/assets/stylesheets/page_bundles/alert_management_details.scss
@@ -1,24 +1,26 @@
+@import 'mixins_and_variables_and_functions';
+
.alert-management-details {
@include media-breakpoint-down(xs) {
.alert-details-incident-button {
- width: 100%;
+ @include gl-w-full;
}
}
.toggle-sidebar-mobile-button {
- right: 0;
+ @include gl-right-0;
}
.dropdown-menu-toggle {
&:hover {
- background-color: $white;
+ @include gl-bg-white;
}
}
.assignee-dropdown-item {
.dropdown-item {
- display: flex;
- align-items: center;
+ @include gl-display-flex;
+ @include gl-align-items-center;
&::before {
top: 50% !important;
@@ -26,7 +28,9 @@
&.is-active {
&:last-child {
- border-bottom: 1px solid $gray-100;
+ @include gl-border-b-gray-100;
+ @include gl-border-b-1;
+ @include gl-border-b-solid;
}
}
}
diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb
index 1b2e6461dee..bc2e7fba288 100644
--- a/app/controllers/concerns/routable_actions.rb
+++ b/app/controllers/concerns/routable_actions.rb
@@ -51,7 +51,7 @@ module RoutableActions
flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path."
end
- redirect_to build_canonical_path(routable)
+ redirect_to build_canonical_path(routable), status: :moved_permanently
end
end
end
diff --git a/app/graphql/types/terraform/state_type.rb b/app/graphql/types/terraform/state_type.rb
index f25f3a7789b..3ee7361baf1 100644
--- a/app/graphql/types/terraform/state_type.rb
+++ b/app/graphql/types/terraform/state_type.rb
@@ -7,6 +7,8 @@ module Types
authorize :read_terraform_state
+ connection_type_class(Types::CountableConnectionType)
+
field :id, GraphQL::ID_TYPE,
null: false,
description: 'ID of the Terraform state'
diff --git a/app/models/ci/test_case.rb b/app/models/ci/test_case.rb
new file mode 100644
index 00000000000..19ecc177436
--- /dev/null
+++ b/app/models/ci/test_case.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Ci
+ class TestCase < ApplicationRecord
+ extend Gitlab::Ci::Model
+
+ validates :project, :key_hash, presence: true
+
+ has_many :test_case_failures, class_name: 'Ci::TestCaseFailure'
+
+ belongs_to :project
+
+ scope :by_project_and_keys, -> (project, keys) { where(project_id: project.id, key_hash: keys) }
+
+ class << self
+ def find_or_create_by_batch(project, test_case_keys)
+ # Insert records first. Existing ones will be skipped.
+ insert_all(test_case_attrs(project, test_case_keys))
+
+ # Find all matching records now that we are sure they all are persisted.
+ by_project_and_keys(project, test_case_keys)
+ end
+
+ private
+
+ def test_case_attrs(project, test_case_keys)
+ # NOTE: Rails 6.1 will add support for insert_all on relation so that
+ # we will be able to do project.test_cases.insert_all.
+ test_case_keys.map do |hashed_key|
+ { project_id: project.id, key_hash: hashed_key }
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/ci/test_case_failure.rb b/app/models/ci/test_case_failure.rb
new file mode 100644
index 00000000000..100d1f039ae
--- /dev/null
+++ b/app/models/ci/test_case_failure.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Ci
+ class TestCaseFailure < ApplicationRecord
+ extend Gitlab::Ci::Model
+
+ validates :test_case, :build, :failed_at, presence: true
+
+ belongs_to :test_case, class_name: "Ci::TestCase", foreign_key: :test_case_id
+ belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id
+ end
+end
diff --git a/app/models/service.rb b/app/models/service.rb
index 0998a9a102a..281bfc6351a 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -274,6 +274,17 @@ class Service < ApplicationRecord
end
end
+ def self.inherited_descendants_from_self_or_ancestors_from(integration)
+ inherit_from_ids =
+ where(type: integration.type, group: integration.group.self_and_ancestors)
+ .or(where(type: integration.type, instance: true)).select(:id)
+
+ from_union([
+ where(type: integration.type, inherit_from_id: inherit_from_ids, group: integration.group.descendants),
+ where(type: integration.type, inherit_from_id: inherit_from_ids, project: Project.in_namespace(integration.group.self_and_descendants))
+ ])
+ end
+
def activated?
active
end
diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb
index b5f034186df..3799266c4dc 100644
--- a/app/services/admin/propagate_integration_service.rb
+++ b/app/services/admin/propagate_integration_service.rb
@@ -5,12 +5,12 @@ module Admin
include PropagateService
def propagate
- update_inherited_integrations
-
if integration.instance?
+ update_inherited_integrations
create_integration_for_groups_without_integration if Feature.enabled?(:group_level_integrations)
create_integration_for_projects_without_integration
else
+ update_inherited_descendant_integrations
create_integration_for_groups_without_integration_belonging_to_group
create_integration_for_projects_without_integration_belonging_to_group
end
@@ -18,34 +18,39 @@ module Admin
private
- # rubocop: disable Cop/InBatches
def update_inherited_integrations
- Service.by_type(integration.type).inherit_from_id(integration.id).each_batch(of: BATCH_SIZE) do |services|
- min_id, max_id = services.pick("MIN(services.id), MAX(services.id)")
- PropagateIntegrationInheritWorker.perform_async(integration.id, min_id, max_id)
- end
+ propagate_integrations(
+ Service.by_type(integration.type).inherit_from_id(integration.id),
+ PropagateIntegrationInheritWorker
+ )
+ end
+
+ def update_inherited_descendant_integrations
+ propagate_integrations(
+ Service.inherited_descendants_from_self_or_ancestors_from(integration),
+ PropagateIntegrationInheritDescendantWorker
+ )
end
- # rubocop: enable Cop/InBatches
def create_integration_for_groups_without_integration
- Group.without_integration(integration).each_batch(of: BATCH_SIZE) do |groups|
- min_id, max_id = groups.pick("MIN(namespaces.id), MAX(namespaces.id)")
- PropagateIntegrationGroupWorker.perform_async(integration.id, min_id, max_id)
- end
+ propagate_integrations(
+ Group.without_integration(integration),
+ PropagateIntegrationGroupWorker
+ )
end
def create_integration_for_groups_without_integration_belonging_to_group
- integration.group.descendants.without_integration(integration).each_batch(of: BATCH_SIZE) do |groups|
- min_id, max_id = groups.pick("MIN(namespaces.id), MAX(namespaces.id)")
- PropagateIntegrationGroupWorker.perform_async(integration.id, min_id, max_id)
- end
+ propagate_integrations(
+ integration.group.descendants.without_integration(integration),
+ PropagateIntegrationGroupWorker
+ )
end
def create_integration_for_projects_without_integration_belonging_to_group
- Project.without_integration(integration).in_namespace(integration.group.self_and_descendants).each_batch(of: BATCH_SIZE) do |projects|
- min_id, max_id = projects.pick("MIN(projects.id), MAX(projects.id)")
- PropagateIntegrationProjectWorker.perform_async(integration.id, min_id, max_id)
- end
+ propagate_integrations(
+ Project.without_integration(integration).in_namespace(integration.group.self_and_descendants),
+ PropagateIntegrationProjectWorker
+ )
end
end
end
diff --git a/app/services/bulk_create_integration_service.rb b/app/services/bulk_create_integration_service.rb
index 2cec7e71989..96bf8a6abaf 100644
--- a/app/services/bulk_create_integration_service.rb
+++ b/app/services/bulk_create_integration_service.rb
@@ -47,7 +47,7 @@ class BulkCreateIntegrationService
if integration.template?
integration.to_service_hash
else
- integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.id }
+ integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.inherit_from_id || integration.id }
end
end
diff --git a/app/services/bulk_update_integration_service.rb b/app/services/bulk_update_integration_service.rb
index 74d77618f2c..bb9bddb5967 100644
--- a/app/services/bulk_update_integration_service.rb
+++ b/app/services/bulk_update_integration_service.rb
@@ -23,7 +23,7 @@ class BulkUpdateIntegrationService
attr_reader :integration, :batch
def service_hash
- integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.id }
+ integration.to_service_hash.tap { |json| json['inherit_from_id'] = integration.inherit_from_id || integration.id }
end
def data_fields_hash
diff --git a/app/services/ci/test_cases_service.rb b/app/services/ci/test_cases_service.rb
new file mode 100644
index 00000000000..3139b567571
--- /dev/null
+++ b/app/services/ci/test_cases_service.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Ci
+ class TestCasesService
+ MAX_TRACKABLE_FAILURES = 200
+
+ def execute(build)
+ return unless Feature.enabled?(:test_failure_history, build.project)
+ return unless build.has_test_reports?
+ return unless build.project.default_branch_or_master == build.ref
+
+ test_suite = generate_test_suite_report(build)
+
+ track_failures(build, test_suite)
+ end
+
+ private
+
+ def generate_test_suite_report(build)
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ end
+
+ def track_failures(build, test_suite)
+ return if test_suite.failed_count > MAX_TRACKABLE_FAILURES
+
+ test_suite.failed.keys.each_slice(100) do |keys|
+ Ci::TestCase.transaction do
+ test_cases = Ci::TestCase.find_or_create_by_batch(build.project, keys)
+ Ci::TestCaseFailure.insert_all(test_case_failures(test_cases, build))
+ end
+ end
+ end
+
+ def test_case_failures(test_cases, build)
+ test_cases.map do |test_case|
+ {
+ test_case_id: test_case.id,
+ build_id: build.id,
+ failed_at: build.finished_at
+ }
+ end
+ end
+ end
+end
diff --git a/app/services/concerns/admin/propagate_service.rb b/app/services/concerns/admin/propagate_service.rb
index 065ab6f7ff9..03e422aec54 100644
--- a/app/services/concerns/admin/propagate_service.rb
+++ b/app/services/concerns/admin/propagate_service.rb
@@ -21,9 +21,16 @@ module Admin
attr_reader :integration
def create_integration_for_projects_without_integration
- Project.without_integration(integration).each_batch(of: BATCH_SIZE) do |projects|
- min_id, max_id = projects.pick("MIN(projects.id), MAX(projects.id)")
- PropagateIntegrationProjectWorker.perform_async(integration.id, min_id, max_id)
+ propagate_integrations(
+ Project.without_integration(integration),
+ PropagateIntegrationProjectWorker
+ )
+ end
+
+ def propagate_integrations(relation, worker_class)
+ relation.each_batch(of: BATCH_SIZE) do |records|
+ min_id, max_id = records.pick("MIN(#{relation.table_name}.id), MAX(#{relation.table_name}.id)")
+ worker_class.perform_async(integration.id, min_id, max_id)
end
end
end
diff --git a/app/views/admin/dev_ops_report/_report.html.haml b/app/views/admin/dev_ops_report/_report.html.haml
new file mode 100644
index 00000000000..7e464bcde3b
--- /dev/null
+++ b/app/views/admin/dev_ops_report/_report.html.haml
@@ -0,0 +1,20 @@
+.devops
+ .devops-header
+ %h2.devops-header-title{ class: "devops-#{score_level(@metric.average_percentage_score)}-score" }
+ = number_to_percentage(@metric.average_percentage_score, precision: 1)
+ .devops-header-subtitle
+ = _('DevOps')
+ %br
+ = _('Score')
+ = link_to sprite_icon('question-o', css_class: 'devops-header-icon'), help_page_path('user/admin_area/analytics/dev_ops_report')
+
+ .devops-cards.board-card-container
+ - @metric.cards.each do |card|
+ = render 'card', card: card
+
+ .devops-steps.d-none.d-lg-block
+ - @metric.idea_to_production_steps.each_with_index do |step, index|
+ .devops-step{ class: "devops-#{score_level(step.percentage_score)}-score" }
+ = custom_icon("i2p_step_#{index + 1}")
+ %h4.devops-step-title
+ = step.title
diff --git a/app/views/admin/dev_ops_report/show.html.haml b/app/views/admin/dev_ops_report/show.html.haml
index 88105be70fb..2ebdadb99a6 100644
--- a/app/views/admin/dev_ops_report/show.html.haml
+++ b/app/views/admin/dev_ops_report/show.html.haml
@@ -12,23 +12,4 @@
- elsif @metric.blank?
= render 'no_data'
- else
- .devops
- .devops-header
- %h2.devops-header-title{ class: "devops-#{score_level(@metric.average_percentage_score)}-score" }
- = number_to_percentage(@metric.average_percentage_score, precision: 1)
- .devops-header-subtitle
- = _('DevOps')
- %br
- = _('Score')
- = link_to sprite_icon('question-o', css_class: 'devops-header-icon'), help_page_path('user/admin_area/analytics/dev_ops_report')
-
- .devops-cards.board-card-container
- - @metric.cards.each do |card|
- = render 'card', card: card
-
- .devops-steps.d-none.d-lg-block
- - @metric.idea_to_production_steps.each_with_index do |step, index|
- .devops-step{ class: "devops-#{score_level(step.percentage_score)}-score" }
- = custom_icon("i2p_step_#{index + 1}")
- %h4.devops-step-title
- = step.title
+ = render 'report'
diff --git a/app/views/projects/alert_management/details.html.haml b/app/views/projects/alert_management/details.html.haml
index 5230d5e3476..b1d680e4f3d 100644
--- a/app/views/projects/alert_management/details.html.haml
+++ b/app/views/projects/alert_management/details.html.haml
@@ -1,4 +1,5 @@
- add_to_breadcrumbs s_('AlertManagement|Alerts'), project_alert_management_index_path(@project)
- page_title s_('AlertManagement|Alert detail')
+- add_page_specific_style 'page_bundles/alert_management_details'
#js-alert_details{ data: alert_management_detail_data(@project, @alert_id) }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 6aded5b8d4b..bde1fd34138 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1847,6 +1847,14 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: propagate_integration_inherit_descendant
+ :feature_category: :integrations
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: propagate_integration_project
:feature_category: :integrations
:has_external_dependencies:
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index d7a5fcf4f18..af2305528ce 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -33,6 +33,11 @@ class BuildFinishedWorker # rubocop:disable Scalability/IdempotentWorker
BuildCoverageWorker.new.perform(build.id)
Ci::BuildReportResultWorker.new.perform(build.id)
+ # TODO: As per https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/194, it may be
+ # best to avoid creating more workers that we have no intention of calling async.
+ # Change the previous worker calls on top to also just call the service directly.
+ Ci::TestCasesService.new.execute(build)
+
# We execute these async as these are independent operations.
BuildHooksWorker.perform_async(build.id)
ExpirePipelineCacheWorker.perform_async(build.pipeline_id) if build.pipeline.cacheable?
diff --git a/app/workers/propagate_integration_inherit_descendant_worker.rb b/app/workers/propagate_integration_inherit_descendant_worker.rb
new file mode 100644
index 00000000000..d589619818c
--- /dev/null
+++ b/app/workers/propagate_integration_inherit_descendant_worker.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class PropagateIntegrationInheritDescendantWorker
+ include ApplicationWorker
+
+ feature_category :integrations
+ idempotent!
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(integration_id, min_id, max_id)
+ integration = Service.find_by_id(integration_id)
+ return unless integration
+
+ batch = Service.inherited_descendants_from_self_or_ancestors_from(integration).where(id: min_id..max_id)
+
+ BulkUpdateIntegrationService.new(integration, batch).execute
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+end
diff --git a/app/workers/propagate_integration_inherit_worker.rb b/app/workers/propagate_integration_inherit_worker.rb
index ef3132202f6..40d67c6d3bf 100644
--- a/app/workers/propagate_integration_inherit_worker.rb
+++ b/app/workers/propagate_integration_inherit_worker.rb
@@ -11,9 +11,9 @@ class PropagateIntegrationInheritWorker
integration = Service.find_by_id(integration_id)
return unless integration
- services = Service.where(id: min_id..max_id).by_type(integration.type).inherit_from_id(integration.id)
+ batch = Service.where(id: min_id..max_id).by_type(integration.type).inherit_from_id(integration.id)
- BulkUpdateIntegrationService.new(integration, services).execute
+ BulkUpdateIntegrationService.new(integration, batch).execute
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/changelogs/unreleased/259024-labels-api-search-support.yml b/changelogs/unreleased/259024-labels-api-search-support.yml
new file mode 100644
index 00000000000..a5ceacb4e20
--- /dev/null
+++ b/changelogs/unreleased/259024-labels-api-search-support.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for search and inclusion of project labels within Group Labels API
+merge_request: 44415
+author:
+type: changed
diff --git a/changelogs/unreleased/267147-graphql-total-count.yml b/changelogs/unreleased/267147-graphql-total-count.yml
new file mode 100644
index 00000000000..7c86664560c
--- /dev/null
+++ b/changelogs/unreleased/267147-graphql-total-count.yml
@@ -0,0 +1,5 @@
+---
+title: Add total count to Terraform state GraphQL API
+merge_request: 45798
+author:
+type: changed
diff --git a/changelogs/unreleased/eb-test-failure-history-mvc.yml b/changelogs/unreleased/eb-test-failure-history-mvc.yml
new file mode 100644
index 00000000000..f05684787f8
--- /dev/null
+++ b/changelogs/unreleased/eb-test-failure-history-mvc.yml
@@ -0,0 +1,5 @@
+---
+title: Store test failure data when build finishes
+merge_request: 45027
+author:
+type: added
diff --git a/changelogs/unreleased/fj-change-routable-redirect-to-301.yml b/changelogs/unreleased/fj-change-routable-redirect-to-301.yml
new file mode 100644
index 00000000000..63d52404fa7
--- /dev/null
+++ b/changelogs/unreleased/fj-change-routable-redirect-to-301.yml
@@ -0,0 +1,5 @@
+---
+title: Change permanent routable redirect to 301
+merge_request: 45980
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-upgrade-ruby-2-7-2-ci.yml b/changelogs/unreleased/sh-upgrade-ruby-2-7-2-ci.yml
new file mode 100644
index 00000000000..ad9bb48dcf2
--- /dev/null
+++ b/changelogs/unreleased/sh-upgrade-ruby-2-7-2-ci.yml
@@ -0,0 +1,5 @@
+---
+title: Update to Ruby v2.7.2
+merge_request: 44223
+author:
+type: other
diff --git a/changelogs/unreleased/vij-add-snippet-repo-readonly.yml b/changelogs/unreleased/vij-add-snippet-repo-readonly.yml
new file mode 100644
index 00000000000..bda2fdb09ad
--- /dev/null
+++ b/changelogs/unreleased/vij-add-snippet-repo-readonly.yml
@@ -0,0 +1,5 @@
+---
+title: Add repository_read_only column to Snippets
+merge_request: 45868
+author:
+type: added
diff --git a/config/application.rb b/config/application.rb
index 6c28bc94bed..de133414bb0 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -197,6 +197,7 @@ module Gitlab
config.assets.precompile << "page_bundles/reports.css"
config.assets.precompile << "page_bundles/xterm.css"
config.assets.precompile << "page_bundles/wiki.css"
+ config.assets.precompile << "page_bundles/alert_management_details.css"
config.assets.precompile << "lazy_bundles/cropper.css"
config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js"
diff --git a/config/feature_flags/development/test_failure_history.yml b/config/feature_flags/development/test_failure_history.yml
new file mode 100644
index 00000000000..a2b8b4c99ef
--- /dev/null
+++ b/config/feature_flags/development/test_failure_history.yml
@@ -0,0 +1,7 @@
+---
+name: test_failure_history
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45027
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/268249
+type: development
+group: group::testing
+default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 423cc43d712..258a75f47fb 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -254,6 +254,8 @@
- 1
- - propagate_integration_inherit
- 1
+- - propagate_integration_inherit_descendant
+ - 1
- - propagate_integration_project
- 1
- - propagate_service_template
diff --git a/db/migrate/20201012134230_create_ci_test_cases.rb b/db/migrate/20201012134230_create_ci_test_cases.rb
new file mode 100644
index 00000000000..fd1bcaf1093
--- /dev/null
+++ b/db/migrate/20201012134230_create_ci_test_cases.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class CreateCiTestCases < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless table_exists?(:ci_test_cases)
+ create_table :ci_test_cases do |t|
+ t.bigint :project_id, null: false
+ t.text :key_hash, null: false
+
+ t.index [:project_id, :key_hash], unique: true
+ # NOTE: FK for projects will be added on a separate migration as per guidelines
+ end
+ end
+
+ add_text_limit :ci_test_cases, :key_hash, 64
+ end
+
+ def down
+ drop_table :ci_test_cases
+ end
+end
diff --git a/db/migrate/20201012135330_create_ci_test_case_failures.rb b/db/migrate/20201012135330_create_ci_test_case_failures.rb
new file mode 100644
index 00000000000..7eaf7b5256d
--- /dev/null
+++ b/db/migrate/20201012135330_create_ci_test_case_failures.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreateCiTestCaseFailures < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ create_table :ci_test_case_failures do |t|
+ t.datetime_with_timezone :failed_at
+ t.bigint :test_case_id, null: false
+ t.bigint :build_id, null: false
+
+ t.index [:test_case_id, :failed_at, :build_id], name: 'index_test_case_failures_unique_columns', unique: true, order: { failed_at: :desc }
+ t.index :build_id
+ t.foreign_key :ci_test_cases, column: :test_case_id, on_delete: :cascade
+ # NOTE: FK for ci_builds will be added on a separate migration as per guidelines
+ end
+ end
+
+ def down
+ drop_table :ci_test_case_failures
+ end
+end
diff --git a/db/migrate/20201012140110_add_projects_fk_to_ci_test_cases.rb b/db/migrate/20201012140110_add_projects_fk_to_ci_test_cases.rb
new file mode 100644
index 00000000000..d30b332e41a
--- /dev/null
+++ b/db/migrate/20201012140110_add_projects_fk_to_ci_test_cases.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddProjectsFkToCiTestCases < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :ci_test_cases, :projects, column: :project_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :ci_test_cases, column: :project_id
+ end
+ end
+end
diff --git a/db/migrate/20201012140452_add_ci_builds_fk_to_ci_test_case_failures.rb b/db/migrate/20201012140452_add_ci_builds_fk_to_ci_test_case_failures.rb
new file mode 100644
index 00000000000..0f2fdf071fa
--- /dev/null
+++ b/db/migrate/20201012140452_add_ci_builds_fk_to_ci_test_case_failures.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddCiBuildsFkToCiTestCaseFailures < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :ci_test_case_failures, :ci_builds, column: :build_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :ci_test_case_failures, column: :build_id
+ end
+ end
+end
diff --git a/db/migrate/20201022080802_add_repository_read_only_to_snippets.rb b/db/migrate/20201022080802_add_repository_read_only_to_snippets.rb
new file mode 100644
index 00000000000..d0cb329fb02
--- /dev/null
+++ b/db/migrate/20201022080802_add_repository_read_only_to_snippets.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddRepositoryReadOnlyToSnippets < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :snippets, :repository_read_only, :boolean, default: false, null: false
+ end
+end
diff --git a/db/schema_migrations/20201012134230 b/db/schema_migrations/20201012134230
new file mode 100644
index 00000000000..2bf2175ef60
--- /dev/null
+++ b/db/schema_migrations/20201012134230
@@ -0,0 +1 @@
+1673018885366e92eb47f5fc705ea8251c2db49b5c14b788e84b10d8db91af48 \ No newline at end of file
diff --git a/db/schema_migrations/20201012135330 b/db/schema_migrations/20201012135330
new file mode 100644
index 00000000000..8ecce197faa
--- /dev/null
+++ b/db/schema_migrations/20201012135330
@@ -0,0 +1 @@
+18ccd2059d9a19a51ea0162c46a1293e280759daffa54ba58ba5e431ee7aba93 \ No newline at end of file
diff --git a/db/schema_migrations/20201012140110 b/db/schema_migrations/20201012140110
new file mode 100644
index 00000000000..1bbfe820a5d
--- /dev/null
+++ b/db/schema_migrations/20201012140110
@@ -0,0 +1 @@
+e266655483655e1ecbb4f65594ef5b985c3f0449231755f589f3e293e28c9f6b \ No newline at end of file
diff --git a/db/schema_migrations/20201012140452 b/db/schema_migrations/20201012140452
new file mode 100644
index 00000000000..11bc272341d
--- /dev/null
+++ b/db/schema_migrations/20201012140452
@@ -0,0 +1 @@
+d62928276708c26656070f803ea6271be74a1fe9802877258d4a8cf19df32d09 \ No newline at end of file
diff --git a/db/schema_migrations/20201022080802 b/db/schema_migrations/20201022080802
new file mode 100644
index 00000000000..c84c8c6ffbe
--- /dev/null
+++ b/db/schema_migrations/20201022080802
@@ -0,0 +1 @@
+809d93d367ff9310063904ee3c266914311ef54e8c7f9d6d7fd924d25890bf19 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 82067d906a2..6efa45850e6 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10656,6 +10656,38 @@ CREATE SEQUENCE ci_subscriptions_projects_id_seq
ALTER SEQUENCE ci_subscriptions_projects_id_seq OWNED BY ci_subscriptions_projects.id;
+CREATE TABLE ci_test_case_failures (
+ id bigint NOT NULL,
+ failed_at timestamp with time zone,
+ test_case_id bigint NOT NULL,
+ build_id bigint NOT NULL
+);
+
+CREATE SEQUENCE ci_test_case_failures_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE ci_test_case_failures_id_seq OWNED BY ci_test_case_failures.id;
+
+CREATE TABLE ci_test_cases (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ key_hash text NOT NULL,
+ CONSTRAINT check_dd3c5d1c15 CHECK ((char_length(key_hash) <= 64))
+);
+
+CREATE SEQUENCE ci_test_cases_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE ci_test_cases_id_seq OWNED BY ci_test_cases.id;
+
CREATE TABLE ci_trigger_requests (
id integer NOT NULL,
trigger_id integer NOT NULL,
@@ -16157,7 +16189,8 @@ CREATE TABLE snippets (
description_html text,
encrypted_secret_token character varying(255),
encrypted_secret_token_iv character varying(255),
- secret boolean DEFAULT false NOT NULL
+ secret boolean DEFAULT false NOT NULL,
+ repository_read_only boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE snippets_id_seq
@@ -17534,6 +17567,10 @@ ALTER TABLE ONLY ci_stages ALTER COLUMN id SET DEFAULT nextval('ci_stages_id_seq
ALTER TABLE ONLY ci_subscriptions_projects ALTER COLUMN id SET DEFAULT nextval('ci_subscriptions_projects_id_seq'::regclass);
+ALTER TABLE ONLY ci_test_case_failures ALTER COLUMN id SET DEFAULT nextval('ci_test_case_failures_id_seq'::regclass);
+
+ALTER TABLE ONLY ci_test_cases ALTER COLUMN id SET DEFAULT nextval('ci_test_cases_id_seq'::regclass);
+
ALTER TABLE ONLY ci_trigger_requests ALTER COLUMN id SET DEFAULT nextval('ci_trigger_requests_id_seq'::regclass);
ALTER TABLE ONLY ci_triggers ALTER COLUMN id SET DEFAULT nextval('ci_triggers_id_seq'::regclass);
@@ -18574,6 +18611,12 @@ ALTER TABLE ONLY ci_stages
ALTER TABLE ONLY ci_subscriptions_projects
ADD CONSTRAINT ci_subscriptions_projects_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY ci_test_case_failures
+ ADD CONSTRAINT ci_test_case_failures_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY ci_test_cases
+ ADD CONSTRAINT ci_test_cases_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY ci_trigger_requests
ADD CONSTRAINT ci_trigger_requests_pkey PRIMARY KEY (id);
@@ -20238,6 +20281,10 @@ CREATE INDEX index_ci_subscriptions_projects_on_upstream_project_id ON ci_subscr
CREATE UNIQUE INDEX index_ci_subscriptions_projects_unique_subscription ON ci_subscriptions_projects USING btree (downstream_project_id, upstream_project_id);
+CREATE INDEX index_ci_test_case_failures_on_build_id ON ci_test_case_failures USING btree (build_id);
+
+CREATE UNIQUE INDEX index_ci_test_cases_on_project_id_and_key_hash ON ci_test_cases USING btree (project_id, key_hash);
+
CREATE INDEX index_ci_trigger_requests_on_commit_id ON ci_trigger_requests USING btree (commit_id);
CREATE INDEX index_ci_trigger_requests_on_trigger_id_and_id ON ci_trigger_requests USING btree (trigger_id, id DESC);
@@ -21740,6 +21787,8 @@ CREATE UNIQUE INDEX index_terraform_states_on_project_id_and_name ON terraform_s
CREATE UNIQUE INDEX index_terraform_states_on_uuid ON terraform_states USING btree (uuid);
+CREATE UNIQUE INDEX index_test_case_failures_unique_columns ON ci_test_case_failures USING btree (test_case_id, failed_at DESC, build_id);
+
CREATE INDEX index_timelogs_on_issue_id ON timelogs USING btree (issue_id);
CREATE INDEX index_timelogs_on_merge_request_id ON timelogs USING btree (merge_request_id);
@@ -22328,6 +22377,9 @@ ALTER TABLE ONLY clusters_applications_runners
ALTER TABLE ONLY design_management_designs_versions
ADD CONSTRAINT fk_03c671965c FOREIGN KEY (design_id) REFERENCES design_management_designs(id) ON DELETE CASCADE;
+ALTER TABLE ONLY ci_test_cases
+ ADD CONSTRAINT fk_0526c30ded FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_05f1e72feb FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
@@ -22841,6 +22893,9 @@ ALTER TABLE ONLY ci_sources_pipelines
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_d5af95fcd9 FOREIGN KEY (lfs_object_deleted_event_id) REFERENCES geo_lfs_object_deleted_events(id) ON DELETE CASCADE;
+ALTER TABLE ONLY ci_test_case_failures
+ ADD CONSTRAINT fk_d69404d827 FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_d6cf4279f7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@@ -24134,6 +24189,9 @@ ALTER TABLE ONLY merge_request_blocks
ALTER TABLE ONLY protected_branch_unprotect_access_levels
ADD CONSTRAINT fk_rails_e9eb8dc025 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
+ALTER TABLE ONLY ci_test_case_failures
+ ADD CONSTRAINT fk_rails_eab6349715 FOREIGN KEY (test_case_id) REFERENCES ci_test_cases(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY alert_management_alert_user_mentions
ADD CONSTRAINT fk_rails_eb2de0cdef FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index ac972e2e33e..825bee93714 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -96,9 +96,9 @@ From there, you can see the following actions:
- Permission to approve merge requests by authors was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
- Number of required approvals was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
- Added or removed users and groups from project approval groups ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213603) in GitLab 13.2)
-- Project CI/CD variable added, removed, or protected status changed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30857) in GitLab 13.4.
+- Project CI/CD variable added, removed, or protected status changed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30857) in GitLab 13.4)
-Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events)
+Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events).
### Instance events **(PREMIUM ONLY)**
@@ -113,8 +113,8 @@ To view the server-wide administrator log, visit **Admin Area > Monitoring > Aud
In addition to the group and project events, the following user actions are also
recorded:
-- Failed Logins
- Sign-in events and the authentication type (such as standard, LDAP, or OmniAuth)
+- Failed sign-ins
- Added SSH key
- Added or removed email
- Changed password
@@ -134,7 +134,7 @@ the filter dropdown box. You can further filter by specific group, project, or u
![audit log](img/audit_log.png)
-Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events)
+Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events).
### Missing events
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 8c9d8713b27..f51d3a090b8 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -2308,6 +2308,11 @@ The connection type for ClusterAgent.
"""
type ClusterAgentConnection {
"""
+ Total count of collection
+ """
+ count: Int!
+
+ """
A list of edges.
"""
edges: [ClusterAgentEdge]
@@ -2390,6 +2395,11 @@ The connection type for ClusterAgentToken.
"""
type ClusterAgentTokenConnection {
"""
+ Total count of collection
+ """
+ count: Int!
+
+ """
A list of edges.
"""
edges: [ClusterAgentTokenEdge]
@@ -8725,7 +8735,12 @@ type Group {
"""
Represents vulnerable project counts for each grade
"""
- vulnerabilityGrades: [VulnerableProjectsByGrade!]!
+ vulnerabilityGrades(
+ """
+ Include grades belonging to subgroups
+ """
+ includeSubgroups: Boolean = false
+ ): [VulnerableProjectsByGrade!]!
"""
Vulnerability scanners reported on the project vulnerabilties of the group and its subgroups
@@ -18950,6 +18965,11 @@ The connection type for TerraformState.
"""
type TerraformStateConnection {
"""
+ Total count of collection
+ """
+ count: Int!
+
+ """
A list of edges.
"""
edges: [TerraformStateEdge]
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 387463595ee..ccbb609e079 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -6184,6 +6184,24 @@
"description": "The connection type for ClusterAgent.",
"fields": [
{
+ "name": "count",
+ "description": "Total count of collection",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "edges",
"description": "A list of edges.",
"args": [
@@ -6443,6 +6461,24 @@
"description": "The connection type for ClusterAgentToken.",
"fields": [
{
+ "name": "count",
+ "description": "Total count of collection",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "edges",
"description": "A list of edges.",
"args": [
@@ -23634,7 +23670,16 @@
"name": "vulnerabilityGrades",
"description": "Represents vulnerable project counts for each grade",
"args": [
-
+ {
+ "name": "includeSubgroups",
+ "description": "Include grades belonging to subgroups",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": "false"
+ }
],
"type": {
"kind": "NON_NULL",
@@ -54903,6 +54948,24 @@
"description": "The connection type for TerraformState.",
"fields": [
{
+ "name": "count",
+ "description": "Total count of collection",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "edges",
"description": "A list of edges.",
"args": [
diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md
index 260ee669e08..f11a4d8ccc9 100644
--- a/doc/api/group_labels.md
+++ b/doc/api/group_labels.md
@@ -26,6 +26,9 @@ GET /groups/:id/labels
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543))_ |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
+| `include_descendant_groups` | boolean | no | Include descendant groups. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
+| `only_group_labels` | boolean | no | Toggle to include only group labels or also project labels. Defaults to `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
+| `search` | string | no | Keyword to filter labels by. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true"
@@ -75,6 +78,8 @@ GET /groups/:id/labels/:label_id
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
+| `include_descendant_groups` | boolean | no | Include descendant groups. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
+| `only_group_labels` | boolean | no | Toggle to include only group labels or also project labels. Defaults to `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/labels/bug"
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 30290f18653..844b09fb0f4 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -24,6 +24,7 @@ GET /projects/:id/labels
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543))_ |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
+| `search` | string | no | Keyword to filter labels by. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true"
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 0adf09595e4..597357f1349 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -243,9 +243,9 @@ Download Ruby and compile it:
```shell
mkdir /tmp/ruby && cd /tmp/ruby
-curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.6.tar.gz
-echo '2d78048e293817f38d4ede4ebc7873013e97bb0b ruby-2.6.6.tar.gz' | shasum -c - && tar xzf ruby-2.6.6.tar.gz
-cd ruby-2.6.6
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.2.tar.gz
+echo 'cb9731a17487e0ad84037490a6baf8bfa31a09e8 ruby-2.7.2.tar.gz' | shasum -c - && tar xzf ruby-2.7.2.tar.gz
+cd ruby-2.7.2
./configure --disable-install-rdoc
make
diff --git a/doc/operations/metrics/dashboards/variables.md b/doc/operations/metrics/dashboards/variables.md
index 2103f8e66db..e1f5f0ce6f4 100644
--- a/doc/operations/metrics/dashboards/variables.md
+++ b/doc/operations/metrics/dashboards/variables.md
@@ -50,6 +50,8 @@ For example, if the dashboard time range is set to 8 hours, the value of
[Variables can be defined](../../../operations/metrics/dashboards/yaml.md#templating-templating-properties) in a custom dashboard YAML file.
+Variable names are case-sensitive.
+
## Query variables from URL
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214500) in GitLab 13.0.
diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md
index 894c0e14862..0d21b0f4444 100644
--- a/doc/user/compliance/license_compliance/index.md
+++ b/doc/user/compliance/license_compliance/index.md
@@ -775,11 +775,11 @@ or using the appropriate [`ASDF_<tool>_VERSION`](https://asdf-vm.com/#/core-conf
activate the appropriate version.
For example, the following `.tool-versions` file will activate version `12.16.3` of [Node.js](https://nodejs.org/)
-and version `2.6.6` of [Ruby](https://www.ruby-lang.org/).
+and version `2.7.2` of [Ruby](https://www.ruby-lang.org/).
```plaintext
nodejs 12.16.3
-ruby 2.6.6
+ruby 2.7.2
```
The next example shows how to activate the same versions of the tools mentioned above by using environment variables defined in your
@@ -792,7 +792,7 @@ include:
license_scanning:
variables:
ASDF_NODEJS_VERSION: '12.16.3'
- ASDF_RUBY_VERSION: '2.6.6'
+ ASDF_RUBY_VERSION: '2.7.2'
```
A full list of variables can be found in [environment variables](#available-variables).
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 6de38354c5e..8750d541641 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -80,7 +80,10 @@ By default, groups created in:
- GitLab 12.2 or later allow both Owners and Maintainers to create subgroups.
- GitLab 12.1 or earlier only allow Owners to create subgroups.
-This setting can be for any group by an Owner or Administrator.
+The setting can be changed for any group by:
+
+- A group owner. Select the group, and navigate to **Settings > General > Permissions, LFS, 2FA**.
+- An administrator. Navigate to **Admin Area > Overview > Groups**, select the group, and choose **Edit**.
For more information check the
[permissions table](../../permissions.md#group-members-permissions). For a list
diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb
index 8443ddf10ce..8af073c614c 100644
--- a/lib/api/group_labels.rb
+++ b/lib/api/group_labels.rb
@@ -20,10 +20,16 @@ module API
desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
+ optional :include_descendant_groups, type: Boolean, default: false,
+ desc: 'Include descendant groups. This feature was added in GitLab 13.6'
+ optional :only_group_labels, type: Boolean, default: true,
+ desc: 'Toggle to include only group labels or also project labels. This feature was added in GitLab 13.6'
+ optional :search, type: String,
+ desc: 'Keyword to filter labels by. This feature was added in GitLab 13.6'
use :pagination
end
get ':id/labels' do
- get_labels(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_labels(user_group, Entities::GroupLabel, declared_params)
end
desc 'Get a single label' do
@@ -33,9 +39,13 @@ module API
params do
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
+ optional :include_descendant_groups, type: Boolean, default: false,
+ desc: 'Include descendant groups. This feature was added in GitLab 13.6'
+ optional :only_group_labels, type: Boolean, default: true,
+ desc: 'Toggle to include only group labels or also project labels. This feature was added in GitLab 13.6'
end
get ':id/labels/:name' do
- get_label(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_label(user_group, Entities::GroupLabel, declared_params)
end
desc 'Create a new label' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index c8aee1f3479..5fdf0859566 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -89,16 +89,15 @@ module API
@project ||= find_project!(params[:id])
end
- def available_labels_for(label_parent, include_ancestor_groups: true)
- search_params = { include_ancestor_groups: include_ancestor_groups }
-
+ def available_labels_for(label_parent, params = { include_ancestor_groups: true, only_group_labels: true })
if label_parent.is_a?(Project)
- search_params[:project_id] = label_parent.id
+ params.delete(:only_group_labels)
+ params[:project_id] = label_parent.id
else
- search_params.merge!(group_id: label_parent.id, only_group_labels: true)
+ params[:group_id] = label_parent.id
end
- LabelsFinder.new(current_user, search_params).execute
+ LabelsFinder.new(current_user, params).execute
end
def find_user(id)
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index 2fb2d9b79cf..4018f2dec21 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -28,23 +28,23 @@ module API
at_least_one_of :new_name, :color, :description
end
- def find_label(parent, id_or_title, include_ancestor_groups: true)
- labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)
+ def find_label(parent, id_or_title, params = { include_ancestor_groups: true })
+ labels = available_labels_for(parent, params)
label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title)
label || not_found!('Label')
end
- def get_labels(parent, entity, include_ancestor_groups: true)
- present paginate(available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)),
+ def get_labels(parent, entity, params = {})
+ present paginate(available_labels_for(parent, params)),
with: entity,
current_user: current_user,
parent: parent,
with_counts: params[:with_counts]
end
- def get_label(parent, entity, include_ancestor_groups: true)
- label = find_label(parent, params_id_or_title, include_ancestor_groups: include_ancestor_groups)
+ def get_label(parent, entity, params = {})
+ label = find_label(parent, params_id_or_title, params)
present label, with: entity, current_user: current_user, parent: parent
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 0cc9f33bd07..c9a75583cee 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -19,10 +19,12 @@ module API
desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
+ optional :search, type: String,
+ desc: 'Keyword to filter labels by. This feature was added in GitLab 13.6'
use :pagination
end
get ':id/labels' do
- get_labels(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_labels(user_project, Entities::ProjectLabel, declared_params)
end
desc 'Get a single label' do
@@ -34,7 +36,7 @@ module API
desc: 'Include ancestor groups'
end
get ':id/labels/:name' do
- get_label(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
+ get_label(user_project, Entities::ProjectLabel, declared_params)
end
desc 'Create a new label' do
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index a0526ba0414..cb75a77e9cf 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -185,6 +185,7 @@ excluded_attributes:
- :secret
- :encrypted_secret_token
- :encrypted_secret_token_iv
+ - :repository_read_only
merge_request_diff:
- :external_diff
- :stored_externally
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b78aac41e24..3cbac2eab55 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7791,6 +7791,9 @@ msgstr ""
msgid "Created on"
msgstr ""
+msgid "Created on %{created_at}"
+msgstr ""
+
msgid "Created on:"
msgstr ""
@@ -15306,6 +15309,9 @@ msgstr ""
msgid "Last used"
msgstr ""
+msgid "Last used %{last_used_at} ago"
+msgstr ""
+
msgid "Last used on:"
msgstr ""
@@ -22938,6 +22944,9 @@ msgstr ""
msgid "SSH host keys are not available on this system. Please use %{ssh_keyscan} command or contact your GitLab administrator for more information."
msgstr ""
+msgid "SSH key"
+msgstr ""
+
msgid "SSH keys allow you to establish a secure connection between your computer and GitLab."
msgstr ""
@@ -23046,6 +23055,9 @@ msgstr ""
msgid "Scopes can't be blank"
msgstr ""
+msgid "Scopes: %{scope_list}"
+msgstr ""
+
msgid "Score"
msgstr ""
@@ -26244,6 +26256,12 @@ msgstr ""
msgid "The following %{user} can also push to this branch: %{branch}"
msgstr ""
+msgid "The following Personal Access Token was revoked by an administrator, %{username}."
+msgstr ""
+
+msgid "The following SSH key was deleted by an administrator, %{username}."
+msgstr ""
+
msgid "The following items will NOT be exported:"
msgstr ""
@@ -30138,6 +30156,15 @@ msgstr ""
msgid "You can always edit this later"
msgstr ""
+msgid "You can create a new %{link}."
+msgstr ""
+
+msgid "You can create a new Personal Access Token by visiting %{link}"
+msgstr ""
+
+msgid "You can create a new SSH key by visiting %{link}"
+msgstr ""
+
msgid "You can create a new one or check them in your %{pat_link_start}personal access tokens%{pat_link_end} settings"
msgstr ""
@@ -30582,6 +30609,9 @@ msgstr ""
msgid "Your License"
msgstr ""
+msgid "Your Personal Access Token was revoked"
+msgstr ""
+
msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
msgstr ""
@@ -30597,6 +30627,9 @@ msgstr ""
msgid "Your Public Email will be displayed on your public profile."
msgstr ""
+msgid "Your SSH key was deleted"
+msgstr ""
+
msgid "Your SSH keys (%{count})"
msgstr ""
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 6310e4b290d..2f675cd71f0 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,4 +1,4 @@
-FROM ruby:2.6-stretch
+FROM ruby:2.7-buster
LABEL maintainer="GitLab Quality Department <quality@gitlab.com>"
ENV DEBIAN_FRONTEND="noninteractive"
@@ -65,7 +65,7 @@ COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/
COPY ./lib/gitlab.rb /home/gitlab/lib/
COPY ./lib/gitlab/utils.rb /home/gitlab/lib/gitlab/
COPY ./INSTALLATION_TYPE ./VERSION /home/gitlab/
-RUN cd /home/gitlab/qa/ && bundle install --jobs=$(nproc) --retry=3 --quiet
+RUN cd /home/gitlab/qa/ && gem install bundler:1.17.3 && bundle install --jobs=$(nproc) --retry=3 --without=development --quiet
COPY ./qa /home/gitlab/qa
ENTRYPOINT ["bin/test"]
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index b2a36f92ffe..2a0823d648e 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -29,7 +29,7 @@ module QA
end
def executor_image
- @executor_image || 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6'
+ @executor_image || 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7'
end
def fabricate_via_api!
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index e15047a0f1d..a5b129eb1f9 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -21,7 +21,7 @@ module QA
@name = name || "qa-runner-#{SecureRandom.hex(4)}"
@run_untagged = true
@executor = :shell
- @executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6'
+ @executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7'
super()
end
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index e95f20bc26c..ccb4ab2b880 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -5,6 +5,8 @@ export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
export BUNDLE_INSTALL_FLAGS=${BUNDLE_INSTALL_FLAGS:-"--without=production development --jobs=$(nproc) --path=vendor --retry=3 --quiet"}
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
+ # This is for backwards compatibility for Gitaly
+ run_timed_command "gem install bundler:1.17.3"
bundle --version
run_timed_command "bundle install --clean ${BUNDLE_INSTALL_FLAGS}"
run_timed_command "bundle check"
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index f956baa0e22..26e1842468b 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -52,14 +52,14 @@ RSpec.describe Projects::IssuesController do
get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to redirect_to(project_issues_path(new_project))
- expect(response).to have_gitlab_http_status(:found)
+ expect(response).to have_gitlab_http_status(:moved_permanently)
end
it 'redirects from an old issue correctly' do
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue }
expect(response).to redirect_to(project_issue_path(new_project, issue))
- expect(response).to have_gitlab_http_status(:found)
+ expect(response).to have_gitlab_http_status(:moved_permanently)
end
end
end
@@ -1869,7 +1869,7 @@ RSpec.describe Projects::IssuesController do
}
expect(response).to redirect_to(designs_project_issue_path(new_project, issue))
- expect(response).to have_gitlab_http_status(:found)
+ expect(response).to have_gitlab_http_status(:moved_permanently)
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index ee194e5ff2f..fefb80a44cc 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -161,7 +161,7 @@ RSpec.describe Projects::MergeRequestsController do
}
expect(response).to redirect_to(project_merge_request_path(new_project, merge_request))
- expect(response).to have_gitlab_http_status(:found)
+ expect(response).to have_gitlab_http_status(:moved_permanently)
end
it 'redirects from an old merge request commits correctly' do
@@ -173,7 +173,7 @@ RSpec.describe Projects::MergeRequestsController do
}
expect(response).to redirect_to(commits_project_merge_request_path(new_project, merge_request))
- expect(response).to have_gitlab_http_status(:found)
+ expect(response).to have_gitlab_http_status(:moved_permanently)
end
end
end
diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb
index 098fa9bac2c..83290e45c11 100644
--- a/spec/controllers/projects/registry/repositories_controller_spec.rb
+++ b/spec/controllers/projects/registry/repositories_controller_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Projects::Registry::RepositoriesController do
end
it 'tracks the event' do
- expect(Gitlab::Tracking).to receive(:event).with(anything, 'list_repositories', {})
+ expect(Gitlab::Tracking).to receive(:event).with(anything, 'list_repositories')
go_to_index(format: :json)
end
@@ -133,7 +133,7 @@ RSpec.describe Projects::Registry::RepositoriesController do
end
it 'tracks the event' do
- expect(Gitlab::Tracking).to receive(:event).with(anything, 'delete_repository', {})
+ expect(Gitlab::Tracking).to receive(:event).with(anything, 'delete_repository')
allow(DeleteContainerRepositoryWorker).to receive(:perform_async).with(user.id, repository.id)
delete_repository(repository)
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index 59df9e78a3c..ab1abf01c71 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Projects::Registry::TagsController do
end
it 'tracks the event' do
- expect(Gitlab::Tracking).to receive(:event).with(anything, 'list_tags', {})
+ expect(Gitlab::Tracking).to receive(:event).with(anything, 'list_tags')
get_tags
end
@@ -109,7 +109,7 @@ RSpec.describe Projects::Registry::TagsController do
it 'tracks the event' do
expect_delete_tags(%w[test.])
- expect(controller).to receive(:track_event).with(:delete_tag, {})
+ expect(controller).to receive(:track_event).with(:delete_tag)
destroy_tag('test.')
end
@@ -150,7 +150,7 @@ RSpec.describe Projects::Registry::TagsController do
it 'tracks the event' do
expect_delete_tags(tags)
- expect(Gitlab::Tracking).to receive(:event).with(anything, 'delete_tag_bulk', {})
+ expect(Gitlab::Tracking).to receive(:event).with(anything, 'delete_tag_bulk')
bulk_destroy_tags(tags)
end
diff --git a/spec/factories/audit_events.rb b/spec/factories/audit_events.rb
index 5497648273c..4e72976a9e5 100644
--- a/spec/factories/audit_events.rb
+++ b/spec/factories/audit_events.rb
@@ -4,7 +4,7 @@ FactoryBot.define do
factory :audit_event, class: 'AuditEvent', aliases: [:user_audit_event] do
user
- transient { target_user { create(:user) } }
+ transient { target_user { association(:user) } }
entity_type { 'User' }
entity_id { target_user.id }
@@ -27,7 +27,7 @@ FactoryBot.define do
end
trait :project_event do
- transient { target_project { create(:project) } }
+ transient { target_project { association(:project) } }
entity_type { 'Project' }
entity_id { target_project.id }
@@ -50,7 +50,7 @@ FactoryBot.define do
end
trait :group_event do
- transient { target_group { create(:group) } }
+ transient { target_group { association(:group) } }
entity_type { 'Group' }
entity_id { target_group.id }
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 73920b76025..07bfde25fd9 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -332,6 +332,12 @@ FactoryBot.define do
end
end
+ trait :test_reports_with_duplicate_failed_test_names do
+ after(:build) do |build|
+ build.job_artifacts << create(:ci_job_artifact, :junit_with_duplicate_failed_test_names, job: build)
+ end
+ end
+
trait :accessibility_reports do
after(:build) do |build|
build.job_artifacts << create(:ci_job_artifact, :accessibility, job: build)
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 1bd4b2826c4..de505ef8203 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -109,6 +109,16 @@ FactoryBot.define do
end
end
+ trait :junit_with_duplicate_failed_test_names do
+ file_type { :junit }
+ file_format { :gzip }
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = fixture_file_upload(
+ Rails.root.join('spec/fixtures/junit/junit_with_duplicate_failed_test_names.xml.gz'), 'application/x-gzip')
+ end
+ end
+
trait :junit_with_ant do
file_type { :junit }
file_format { :gzip }
diff --git a/spec/factories/ci/reports/test_case.rb b/spec/factories/ci/reports/test_case.rb
new file mode 100644
index 00000000000..0626de9d6dc
--- /dev/null
+++ b/spec/factories/ci/reports/test_case.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :report_test_case, class: 'Gitlab::Ci::Reports::TestCase' do
+ suite_name { "rspec" }
+ name { "test-1" }
+ classname { "trace" }
+ file { "spec/trace_spec.rb" }
+ execution_time { 1.23 }
+ status { Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS }
+ system_output { nil }
+ attachment { nil }
+ association :job, factory: :ci_build
+
+ trait :failed do
+ status { Gitlab::Ci::Reports::TestCase::STATUS_FAILED }
+ system_output { "Failure/Error: is_expected.to eq(300) expected: 300 got: -100" }
+ end
+
+ trait :failed_with_attachment do
+ status { Gitlab::Ci::Reports::TestCase::STATUS_FAILED }
+ attachment { "some/path.png" }
+ end
+
+ skip_create
+
+ initialize_with do
+ new(
+ suite_name: suite_name,
+ name: name,
+ classname: classname,
+ file: file,
+ execution_time: execution_time,
+ status: status,
+ system_output: system_output,
+ attachment: attachment,
+ job: job
+ )
+ end
+ end
+end
diff --git a/spec/factories/ci/test_case.rb b/spec/factories/ci/test_case.rb
index 7f99f0e123e..601a3fae970 100644
--- a/spec/factories/ci/test_case.rb
+++ b/spec/factories/ci/test_case.rb
@@ -1,41 +1,8 @@
# frozen_string_literal: true
FactoryBot.define do
- factory :test_case, class: 'Gitlab::Ci::Reports::TestCase' do
- suite_name { "rspec" }
- name { "test-1" }
- classname { "trace" }
- file { "spec/trace_spec.rb" }
- execution_time { 1.23 }
- status { Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS }
- system_output { nil }
- attachment { nil }
- association :job, factory: :ci_build
-
- trait :failed do
- status { Gitlab::Ci::Reports::TestCase::STATUS_FAILED }
- system_output { "Failure/Error: is_expected.to eq(300) expected: 300 got: -100" }
- end
-
- trait :failed_with_attachment do
- status { Gitlab::Ci::Reports::TestCase::STATUS_FAILED }
- attachment { "some/path.png" }
- end
-
- skip_create
-
- initialize_with do
- new(
- suite_name: suite_name,
- name: name,
- classname: classname,
- file: file,
- execution_time: execution_time,
- status: status,
- system_output: system_output,
- attachment: attachment,
- job: job
- )
- end
+ factory :ci_test_case, class: 'Ci::TestCase' do
+ project
+ key_hash { Digest::SHA256.hexdigest(SecureRandom.hex) }
end
end
diff --git a/spec/factories/ci/test_case_failure.rb b/spec/factories/ci/test_case_failure.rb
new file mode 100644
index 00000000000..11fb002804b
--- /dev/null
+++ b/spec/factories/ci/test_case_failure.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :ci_test_case_failure, class: 'Ci::TestCaseFailure' do
+ build factory: :ci_build
+ test_case factory: :ci_test_case
+ failed_at { Time.current }
+ end
+end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 5d3a00017f2..84ea9495f08 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -12,6 +12,9 @@ RSpec.describe 'Profile account page', :js do
describe 'when I delete my account' do
before do
visit profile_account_path
+
+ # Scroll page to the bottom to make Delete account button visible
+ execute_script('window.scrollTo(0, document.body.scrollHeight)')
end
it { expect(page).to have_content('Delete account') }
diff --git a/spec/fixtures/junit/junit_with_duplicate_failed_test_names.xml.gz b/spec/fixtures/junit/junit_with_duplicate_failed_test_names.xml.gz
new file mode 100644
index 00000000000..9bcd06759da
--- /dev/null
+++ b/spec/fixtures/junit/junit_with_duplicate_failed_test_names.xml.gz
Binary files differ
diff --git a/spec/graphql/resolvers/release_resolver_spec.rb b/spec/graphql/resolvers/release_resolver_spec.rb
index 666d54fbc3c..04765fc68e9 100644
--- a/spec/graphql/resolvers/release_resolver_spec.rb
+++ b/spec/graphql/resolvers/release_resolver_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Resolvers::ReleaseResolver do
let(:args) { {} }
it 'raises an error' do
- expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: tag_name")
+ expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: :tag_name")
end
end
end
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 8e738af0fa3..38b70428a8e 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -177,7 +177,7 @@ RSpec.describe API::Helpers do
describe '#track_event' do
it "creates a gitlab tracking event" do
- expect(Gitlab::Tracking).to receive(:event).with('foo', 'my_event', {})
+ expect(Gitlab::Tracking).to receive(:event).with('foo', 'my_event')
subject.track_event('my_event', category: 'foo')
end
diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb
index a142846fc18..40c91fc9ffb 100644
--- a/spec/lib/gitlab/ci/reports/test_case_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Reports::TestCase do
context 'when required params are given' do
let(:job) { build(:ci_build) }
- let(:params) { attributes_for(:test_case).merge!(job: job) }
+ let(:params) { attributes_for(:report_test_case).merge!(job: job) }
it 'initializes an instance', :aggregate_failures do
expect { test_case }.not_to raise_error
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Ci::Reports::TestCase do
shared_examples 'param is missing' do |param|
let(:job) { build(:ci_build) }
- let(:params) { attributes_for(:test_case).merge!(job: job) }
+ let(:params) { attributes_for(:report_test_case).merge!(job: job) }
it 'raises an error' do
params.delete(param)
@@ -55,7 +55,7 @@ RSpec.describe Gitlab::Ci::Reports::TestCase do
context 'when attachment is present' do
let_it_be(:job) { create(:ci_build) }
- let(:attachment_test_case) { build(:test_case, :failed_with_attachment, job: job) }
+ let(:attachment_test_case) { build(:report_test_case, :failed_with_attachment, job: job) }
it "initializes the attachment if present" do
expect(attachment_test_case.attachment).to eq("some/path.png")
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Ci::Reports::TestCase do
end
context 'when attachment is missing' do
- let(:test_case) { build(:test_case) }
+ let(:test_case) { build(:report_test_case) }
it '#has_attachment?' do
expect(test_case.has_attachment?).to be_falsy
diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
index 502859852f2..24c00de3731 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
@@ -110,7 +110,7 @@ RSpec.describe Gitlab::Ci::Reports::TestReports do
end
describe '#with_attachment' do
- let(:test_case) { build(:test_case, :failed) }
+ let(:test_case) { build(:report_test_case, :failed) }
subject { test_reports.with_attachment! }
@@ -126,8 +126,8 @@ RSpec.describe Gitlab::Ci::Reports::TestReports do
end
context 'when test suites contain an attachment' do
- let(:test_case_succes) { build(:test_case) }
- let(:test_case_with_attachment) { build(:test_case, :failed_with_attachment) }
+ let(:test_case_succes) { build(:report_test_case) }
+ let(:test_case_with_attachment) { build(:report_test_case, :failed_with_attachment) }
before do
test_reports.get_suite('rspec').add_test_case(test_case_succes)
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index 50d1595da73..1d6b39a7831 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -91,7 +91,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do
subject { test_suite.with_attachment! }
context 'when test cases do not contain an attachment' do
- let(:test_case) { build(:test_case, :failed)}
+ let(:test_case) { build(:report_test_case, :failed)}
before do
test_suite.add_test_case(test_case)
@@ -103,7 +103,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do
end
context 'when test cases contain an attachment' do
- let(:test_case_with_attachment) { build(:test_case, :failed_with_attachment)}
+ let(:test_case_with_attachment) { build(:report_test_case, :failed_with_attachment)}
before do
test_suite.add_test_case(test_case_with_attachment)
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index eba2f29836d..2e43f22830a 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -15,14 +15,14 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
context 'when unknown keyword is specified' do
it 'raises error' do
expect { described_class.new(key: variable_key, value: 'abc', files: true) }
- .to raise_error ArgumentError, 'unknown keyword: files'
+ .to raise_error ArgumentError, 'unknown keyword: :files'
end
end
context 'when required keywords are not specified' do
it 'raises error' do
expect { described_class.new(key: variable_key) }
- .to raise_error ArgumentError, 'missing keyword: value'
+ .to raise_error ArgumentError, 'missing keyword: :value'
end
end
diff --git a/spec/lib/gitlab/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
index 2011587a342..f9088130037 100644
--- a/spec/lib/gitlab/config/entry/simplifiable_spec.rb
+++ b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::Config::Entry::Simplifiable do
end
it 'attemps to load a first strategy' do
- expect(first).to receive(:new).with('something', anything)
+ expect(first).to receive(:new).with('something')
entry.new('something')
end
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::Config::Entry::Simplifiable do
end
it 'attemps to load a second strategy' do
- expect(second).to receive(:new).with('test', anything)
+ expect(second).to receive(:new).with('test')
entry.new('test')
end
@@ -68,7 +68,7 @@ RSpec.describe Gitlab::Config::Entry::Simplifiable do
end
it 'instantiates an unknown strategy' do
- expect(unknown).to receive(:new).with('test', anything)
+ expect(unknown).to receive(:new).with('test')
entry.new('test')
end
diff --git a/spec/lib/gitlab/tracking/incident_management_spec.rb b/spec/lib/gitlab/tracking/incident_management_spec.rb
index 9c49c76ead7..fbcb9bf3e4c 100644
--- a/spec/lib/gitlab/tracking/incident_management_spec.rb
+++ b/spec/lib/gitlab/tracking/incident_management_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe Gitlab::Tracking::IncidentManagement do
context 'param without label' do
let(:params) { { create_issue: '1' } }
- it_behaves_like 'a tracked event', "enabled_issue_auto_creation_on_alerts", {}
+ it_behaves_like 'a tracked event', "enabled_issue_auto_creation_on_alerts"
end
end
diff --git a/spec/models/ci/test_case_failure_spec.rb b/spec/models/ci/test_case_failure_spec.rb
new file mode 100644
index 00000000000..664152f6630
--- /dev/null
+++ b/spec/models/ci/test_case_failure_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TestCaseFailure do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:build) }
+ it { is_expected.to belong_to(:test_case) }
+ end
+
+ describe 'validations' do
+ subject { build(:ci_test_case_failure) }
+
+ it { is_expected.to validate_presence_of(:test_case) }
+ it { is_expected.to validate_presence_of(:build) }
+ it { is_expected.to validate_presence_of(:failed_at) }
+ end
+end
diff --git a/spec/models/ci/test_case_spec.rb b/spec/models/ci/test_case_spec.rb
new file mode 100644
index 00000000000..45311e285a6
--- /dev/null
+++ b/spec/models/ci/test_case_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TestCase do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:test_case_failures) }
+ end
+
+ describe 'validations' do
+ subject { build(:ci_test_case) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:key_hash) }
+ end
+
+ describe '.find_or_create_by_batch' do
+ it 'finds or creates records for the given test case keys', :aggregate_failures do
+ project = create(:project)
+ existing_tc = create(:ci_test_case, project: project)
+ new_key = Digest::SHA256.hexdigest(SecureRandom.hex)
+ keys = [existing_tc.key_hash, new_key]
+
+ result = described_class.find_or_create_by_batch(project, keys)
+
+ expect(result.map(&:key_hash)).to match_array([existing_tc.key_hash, new_key])
+ expect(result).to all(be_persisted)
+ end
+ end
+end
diff --git a/spec/models/concerns/optionally_search_spec.rb b/spec/models/concerns/optionally_search_spec.rb
index c8e2e6da51f..8067ad50322 100644
--- a/spec/models/concerns/optionally_search_spec.rb
+++ b/spec/models/concerns/optionally_search_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe OptionallySearch do
it 'delegates to the search method' do
expect(model)
.to receive(:search)
- .with('foo', {})
+ .with('foo')
.and_call_original
expect(model.optionally_search('foo')).to eq(['foo', {}])
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 118b1492cd6..1a791820f1b 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -252,12 +252,17 @@ RSpec.describe Member do
end
describe '.last_ten_days_excluding_today' do
- let_it_be(:created_today) { create(:group_member, created_at: Date.today.beginning_of_day) }
- let_it_be(:created_yesterday) { create(:group_member, created_at: 1.day.ago) }
- let_it_be(:created_eleven_days_ago) { create(:group_member, created_at: 11.days.ago) }
+ let_it_be(:now) { Time.current }
+ let_it_be(:created_today) { create(:group_member, created_at: now.beginning_of_day) }
+ let_it_be(:created_yesterday) { create(:group_member, created_at: now - 1.day) }
+ let_it_be(:created_eleven_days_ago) { create(:group_member, created_at: now - 11.days) }
subject { described_class.last_ten_days_excluding_today }
+ before do
+ travel_to now
+ end
+
it { is_expected.to include(created_yesterday) }
it { is_expected.not_to include(created_today, created_eleven_days_ago) }
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index db3cf19a03f..49a0a46eb85 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -599,6 +599,23 @@ RSpec.describe Service do
end
end
+ describe '.inherited_descendants_from_self_or_ancestors_from' do
+ let_it_be(:subgroup1) { create(:group, parent: group) }
+ let_it_be(:subgroup2) { create(:group, parent: group) }
+ let_it_be(:project1) { create(:project, group: subgroup1) }
+ let_it_be(:project2) { create(:project, group: subgroup2) }
+ let_it_be(:group_integration) { create(:prometheus_service, group: group, project: nil) }
+ let_it_be(:subgroup_integration1) { create(:prometheus_service, group: subgroup1, project: nil, inherit_from_id: group_integration.id) }
+ let_it_be(:subgroup_integration2) { create(:prometheus_service, group: subgroup2, project: nil) }
+ let_it_be(:project_integration1) { create(:prometheus_service, group: nil, project: project1, inherit_from_id: group_integration.id) }
+ let_it_be(:project_integration2) { create(:prometheus_service, group: nil, project: project2, inherit_from_id: subgroup_integration2.id) }
+
+ it 'returns the groups and projects inheriting from integration ancestors', :aggregate_failures do
+ expect(described_class.inherited_descendants_from_self_or_ancestors_from(group_integration)).to eq([subgroup_integration1, project_integration1])
+ expect(described_class.inherited_descendants_from_self_or_ancestors_from(subgroup_integration2)).to eq([project_integration2])
+ end
+ end
+
describe "{property}_changed?" do
let(:service) do
BambooService.create(
diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb
new file mode 100644
index 00000000000..533f913926c
--- /dev/null
+++ b/spec/requests/api/graphql/project/terraform/states_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'query terraform states' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:terraform_state) { create(:terraform_state, :locked, project: project) }
+
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ terraformStates {
+ count
+ nodes {
+ id
+ name
+ lockedAt
+ createdAt
+ updatedAt
+
+ lockedByUser {
+ id
+ }
+ }
+ }
+ })
+ end
+
+ let(:current_user) { project.creator }
+ let(:data) { graphql_data.dig('project', 'terraformStates') }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'returns terraform state data', :aggregate_failures do
+ state = data.dig('nodes', 0)
+
+ expect(state['id']).to eq(terraform_state.to_global_id.to_s)
+ expect(state['name']).to eq(terraform_state.name)
+ expect(state['lockedAt']).to eq(terraform_state.locked_at.strftime('%Y-%m-%dT%H:%M:%SZ'))
+ expect(state['createdAt']).to eq(terraform_state.created_at.strftime('%Y-%m-%dT%H:%M:%SZ'))
+ expect(state['updatedAt']).to eq(terraform_state.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ'))
+ expect(state.dig('lockedByUser', 'id')).to eq(terraform_state.locked_by_user.to_global_id.to_s)
+ end
+
+ it 'returns count of terraform states' do
+ count = data.dig('count')
+ expect(count).to be(project.terraform_states.size)
+ end
+
+ context 'unauthorized users' do
+ let(:current_user) { nil }
+
+ it { expect(data).to be_nil }
+ end
+end
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index f965a845bbe..72621e2ce5e 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -7,60 +7,97 @@ RSpec.describe API::GroupLabels do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let!(:group_member) { create(:group_member, group: group, user: user) }
- let!(:group_label1) { create(:group_label, title: 'feature', group: group) }
+ let!(:group_label1) { create(:group_label, title: 'feature-label', group: group) }
let!(:group_label2) { create(:group_label, title: 'bug', group: group) }
- let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
+ let!(:subgroup_label) { create(:group_label, title: 'support-label', group: subgroup) }
describe 'GET :id/labels' do
- it 'returns all available labels for the group' do
- get api("/groups/#{group.id}/labels", user)
+ context 'get current group labels' do
+ let(:request) { get api("/groups/#{group.id}/labels", user) }
+ let(:expected_labels) { [group_label1.name, group_label2.name] }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response).to all(match_schema('public_api/v4/labels/label'))
- expect(json_response.size).to eq(2)
- expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug')
- end
-
- context 'when the with_counts parameter is set' do
- it 'includes counts in the response' do
- get api("/groups/#{group.id}/labels", user), params: { with_counts: true }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response).to all(match_schema('public_api/v4/labels/label_with_counts'))
- expect(json_response.size).to eq(2)
- expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0)
+ it_behaves_like 'fetches labels'
+
+ context 'when search param is provided' do
+ let(:request) { get api("/groups/#{group.id}/labels?search=lab", user) }
+ let(:expected_labels) { [group_label1.name] }
+
+ it_behaves_like 'fetches labels'
end
- end
- end
- describe 'GET :subgroup_id/labels' do
- context 'when the include_ancestor_groups parameter is not set' do
- it 'returns all available labels for the group and ancestor groups' do
- get api("/groups/#{subgroup.id}/labels", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response).to all(match_schema('public_api/v4/labels/label'))
- expect(json_response.size).to eq(3)
- expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug', 'support')
+ context 'when the with_counts parameter is set' do
+ it 'includes counts in the response' do
+ get api("/groups/#{group.id}/labels", user), params: { with_counts: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/label_with_counts'))
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0)
+ end
+ end
+
+ context 'when include_descendant_groups param is provided' do
+ let!(:project) { create(:project, group: group) }
+ let!(:project_label1) { create(:label, title: 'project-label1', project: project, priority: 3) }
+ let!(:project_label2) { create(:label, title: 'project-bug', project: project) }
+
+ let(:request) { get api("/groups/#{group.id}/labels", user), params: { include_descendant_groups: true } }
+ let(:expected_labels) { [group_label1.name, group_label2.name, subgroup_label.name] }
+
+ it_behaves_like 'fetches labels'
+
+ context 'when search param is provided' do
+ let(:request) { get api("/groups/#{group.id}/labels", user), params: { search: 'lab', include_descendant_groups: true } }
+ let(:expected_labels) { [group_label1.name, subgroup_label.name] }
+
+ it_behaves_like 'fetches labels'
+ end
+
+ context 'when only_group_labels param is false' do
+ let(:request) { get api("/groups/#{group.id}/labels", user), params: { include_descendant_groups: true, only_group_labels: false } }
+ let(:expected_labels) { [group_label1.name, group_label2.name, subgroup_label.name, project_label1.name, project_label2.name] }
+
+ it_behaves_like 'fetches labels'
+
+ context 'when search param is provided' do
+ let(:request) { get api("/groups/#{group.id}/labels", user), params: { search: 'lab', include_descendant_groups: true, only_group_labels: false } }
+ let(:expected_labels) { [group_label1.name, subgroup_label.name, project_label1.name] }
+
+ it_behaves_like 'fetches labels'
+ end
+ end
end
end
- context 'when the include_ancestor_groups parameter is set to false' do
- it 'returns all available labels for the group but not for ancestor groups' do
- get api("/groups/#{subgroup.id}/labels", user), params: { include_ancestor_groups: false }
+ describe 'with subgroup labels' do
+ context 'when the include_ancestor_groups parameter is not set' do
+ let(:request) { get api("/groups/#{subgroup.id}/labels", user) }
+ let(:expected_labels) { [group_label1.name, group_label2.name, subgroup_label.name] }
+
+ it_behaves_like 'fetches labels'
+
+ context 'when search param is provided' do
+ let(:request) { get api("/groups/#{subgroup.id}/labels?search=lab", user) }
+ let(:expected_labels) { [group_label1.name, subgroup_label.name] }
+
+ it_behaves_like 'fetches labels'
+ end
+ end
+
+ context 'when the include_ancestor_groups parameter is set to false' do
+ let(:request) { get api("/groups/#{subgroup.id}/labels", user), params: { include_ancestor_groups: false } }
+ let(:expected_labels) { [subgroup_label.name] }
+
+ it_behaves_like 'fetches labels'
+
+ context 'when search param is provided' do
+ let(:request) { get api("/groups/#{subgroup.id}/labels?search=lab", user), params: { include_ancestor_groups: false } }
+ let(:expected_labels) { [subgroup_label.name] }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response).to all(match_schema('public_api/v4/labels/label'))
- expect(json_response.size).to eq(1)
- expect(json_response.map {|r| r['name'] }).to contain_exactly('support')
+ it_behaves_like 'fetches labels'
+ end
end
end
end
@@ -223,7 +260,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(subgroup.labels[0].name).to eq('New Label')
- expect(group_label1.name).to eq('feature')
+ expect(group_label1.name).to eq(group_label1.title)
end
it 'returns 404 if label does not exist' do
@@ -278,7 +315,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(subgroup.labels[0].name).to eq('New Label')
- expect(group_label1.name).to eq('feature')
+ expect(group_label1.name).to eq(group_label1.title)
end
it 'returns 404 if label does not exist' do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index fc674fca9b2..b368f6e329c 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -178,8 +178,8 @@ RSpec.describe API::Labels do
end
describe 'GET /projects/:id/labels' do
- let(:group) { create(:group) }
- let!(:group_label) { create(:group_label, title: 'feature', group: group) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_label) { create(:group_label, title: 'feature label', group: group) }
before do
project.update!(group: group)
@@ -250,49 +250,41 @@ RSpec.describe API::Labels do
end
end
- context 'when the include_ancestor_groups parameter is not set' do
- let(:group) { create(:group) }
- let!(:group_label) { create(:group_label, title: 'feature', group: group) }
- let(:subgroup) { create(:group, parent: group) }
- let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
+ context 'with subgroups' do
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:subgroup_label) { create(:group_label, title: 'support label', group: subgroup) }
before do
subgroup.add_owner(user)
project.update!(group: subgroup)
end
- it 'returns all available labels for the project, parent group and ancestor groups' do
- get api("/projects/#{project.id}/labels", user)
+ context 'when the include_ancestor_groups parameter is not set' do
+ let(:request) { get api("/projects/#{project.id}/labels", user) }
+ let(:expected_labels) { [priority_label.name, group_label.name, subgroup_label.name, label1.name] }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response).to all(match_schema('public_api/v4/labels/label'))
- expect(json_response.size).to eq(4)
- expect(json_response.map {|r| r['name'] }).to contain_exactly(group_label.name, subgroup_label.name, priority_label.name, label1.name)
- end
- end
+ it_behaves_like 'fetches labels'
- context 'when the include_ancestor_groups parameter is set to false' do
- let(:group) { create(:group) }
- let!(:group_label) { create(:group_label, title: 'feature', group: group) }
- let(:subgroup) { create(:group, parent: group) }
- let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
+ context 'when search param is provided' do
+ let(:request) { get api("/projects/#{project.id}/labels?search=lab", user) }
+ let(:expected_labels) { [group_label.name, subgroup_label.name, label1.name] }
- before do
- subgroup.add_owner(user)
- project.update!(group: subgroup)
+ it_behaves_like 'fetches labels'
+ end
end
- it 'returns all available labels for the project and the parent group only' do
- get api("/projects/#{project.id}/labels", user), params: { include_ancestor_groups: false }
+ context 'when the include_ancestor_groups parameter is set to false' do
+ let(:request) { get api("/projects/#{project.id}/labels", user), params: { include_ancestor_groups: false } }
+ let(:expected_labels) { [subgroup_label.name, priority_label.name, label1.name] }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response).to all(match_schema('public_api/v4/labels/label'))
- expect(json_response.size).to eq(3)
- expect(json_response.map {|r| r['name'] }).to contain_exactly(subgroup_label.name, priority_label.name, label1.name)
+ it_behaves_like 'fetches labels'
+
+ context 'when search param is provided' do
+ let(:request) { get api("/projects/#{project.id}/labels?search=lab", user), params: { include_ancestor_groups: false } }
+ let(:expected_labels) { [subgroup_label.name, label1.name] }
+
+ it_behaves_like 'fetches labels'
+ end
end
end
end
@@ -513,7 +505,7 @@ RSpec.describe API::Labels do
end
describe 'PUT /projects/:id/labels/promote' do
- let(:group) { create(:group) }
+ let_it_be(:group) { create(:group) }
before do
group.add_owner(user)
diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
index 34476b10576..a66165376b0 100644
--- a/spec/requests/api/project_container_repositories_spec.rb
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -310,7 +310,7 @@ RSpec.describe API::ProjectContainerRepositories do
it 'properly removes tag' do
expect(service).to receive(:execute).with(root_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(root_repository.project, api_user, tags: %w[rootA]) { service }
- expect(Gitlab::Tracking).to receive(:event).with(described_class.name, 'delete_tag', {})
+ expect(Gitlab::Tracking).to receive(:event).with(described_class.name, 'delete_tag')
subject
@@ -326,7 +326,7 @@ RSpec.describe API::ProjectContainerRepositories do
it 'properly removes tag' do
expect(service).to receive(:execute).with(root_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(root_repository.project, api_user, tags: %w[rootA]) { service }
- expect(Gitlab::Tracking).to receive(:event).with(described_class.name, 'delete_tag', {})
+ expect(Gitlab::Tracking).to receive(:event).with(described_class.name, 'delete_tag')
subject
diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb
index 45e63e3feec..a77ea5ead5a 100644
--- a/spec/serializers/test_case_entity_spec.rb
+++ b/spec/serializers/test_case_entity_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is present' do
- let(:test_case) { build(:test_case, :failed_with_attachment, job: job) }
+ let(:test_case) { build(:report_test_case, :failed_with_attachment, job: job) }
it 'returns the attachment_url' do
expect(subject).to include(:attachment_url)
@@ -50,7 +50,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is not present' do
- let(:test_case) { build(:test_case, job: job) }
+ let(:test_case) { build(:report_test_case, job: job) }
it 'returns a nil attachment_url' do
expect(subject[:attachment_url]).to be_nil
@@ -64,7 +64,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is present' do
- let(:test_case) { build(:test_case, :failed_with_attachment, job: job) }
+ let(:test_case) { build(:report_test_case, :failed_with_attachment, job: job) }
it 'returns no attachment_url' do
expect(subject).not_to include(:attachment_url)
@@ -72,7 +72,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is not present' do
- let(:test_case) { build(:test_case, job: job) }
+ let(:test_case) { build(:report_test_case, job: job) }
it 'returns no attachment_url' do
expect(subject).not_to include(:attachment_url)
diff --git a/spec/services/admin/propagate_integration_service_spec.rb b/spec/services/admin/propagate_integration_service_spec.rb
index 5df4d9db8b1..5df6e5e50ff 100644
--- a/spec/services/admin/propagate_integration_service_spec.rb
+++ b/spec/services/admin/propagate_integration_service_spec.rb
@@ -67,7 +67,7 @@ RSpec.describe Admin::PropagateIntegrationService do
end
end
- context 'with a group without integration' do
+ context 'with a subgroup without integration' do
let(:subgroup) { create(:group, parent: group) }
it 'calls to PropagateIntegrationGroupWorker' do
@@ -77,6 +77,18 @@ RSpec.describe Admin::PropagateIntegrationService do
described_class.propagate(group_integration)
end
end
+
+ context 'with a subgroup with integration' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:subgroup_integration) { create(:jira_service, group: subgroup, project: nil, inherit_from_id: group_integration.id) }
+
+ it 'calls to PropagateIntegrationInheritDescendantWorker' do
+ expect(PropagateIntegrationInheritDescendantWorker).to receive(:perform_async)
+ .with(group_integration.id, subgroup_integration.id, subgroup_integration.id)
+
+ described_class.propagate(group_integration)
+ end
+ end
end
end
end
diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb
index 140b7a84d40..6278f00e878 100644
--- a/spec/services/bulk_create_integration_service_spec.rb
+++ b/spec/services/bulk_create_integration_service_spec.rb
@@ -9,9 +9,9 @@ RSpec.describe BulkCreateIntegrationService do
stub_jira_service_test
end
+ let_it_be(:instance_integration) { create(:jira_service, :instance) }
+ let_it_be(:template_integration) { create(:jira_service, :template) }
let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] }
- let!(:instance_integration) { create(:jira_service, :instance) }
- let!(:template_integration) { create(:jira_service, :template) }
shared_examples 'creates integration from batch ids' do
it 'updates the inherited integrations' do
@@ -37,7 +37,7 @@ RSpec.describe BulkCreateIntegrationService do
it 'updates inherit_from_id attributes' do
described_class.new(integration, batch, association).execute
- expect(created_integration.reload.inherit_from_id).to eq(integration.id)
+ expect(created_integration.reload.inherit_from_id).to eq(inherit_from_id)
end
end
@@ -79,6 +79,7 @@ RSpec.describe BulkCreateIntegrationService do
context 'with an instance-level integration' do
let(:integration) { instance_integration }
+ let(:inherit_from_id) { integration.id }
context 'with a project association' do
let!(:project) { create(:project) }
@@ -100,13 +101,26 @@ RSpec.describe BulkCreateIntegrationService do
end
context 'with a group association' do
- let!(:group) { create(:group) }
+ let_it_be(:group) { create(:group) }
let(:created_integration) { Service.find_by(group: group) }
let(:batch) { Group.all }
let(:association) { 'group' }
it_behaves_like 'creates integration from batch ids'
it_behaves_like 'updates inherit_from_id'
+
+ context 'with a subgroup association' do
+ let_it_be(:group_integration) { create(:jira_service, group: group, project: nil, inherit_from_id: instance_integration.id) }
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let(:integration) { group_integration }
+ let(:created_integration) { Service.find_by(group: subgroup) }
+ let(:batch) { Group.all }
+ let(:association) { 'group' }
+ let(:inherit_from_id) { instance_integration.id }
+
+ it_behaves_like 'creates integration from batch ids'
+ it_behaves_like 'updates inherit_from_id'
+ end
end
end
@@ -118,6 +132,7 @@ RSpec.describe BulkCreateIntegrationService do
let(:created_integration) { project.jira_service }
let(:batch) { Project.all }
let(:association) { 'project' }
+ let(:inherit_from_id) { integration.id }
it_behaves_like 'creates integration from batch ids'
it_behaves_like 'updates project callbacks'
diff --git a/spec/services/bulk_update_integration_service_spec.rb b/spec/services/bulk_update_integration_service_spec.rb
index 2f0bfd31600..d6022ba5b59 100644
--- a/spec/services/bulk_update_integration_service_spec.rb
+++ b/spec/services/bulk_update_integration_service_spec.rb
@@ -5,14 +5,28 @@ require 'spec_helper'
RSpec.describe BulkUpdateIntegrationService do
include JiraServiceHelper
- before do
+ before_all do
stub_jira_service_test
end
let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] }
- let!(:instance_integration) do
+
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_integration) do
+ JiraService.create!(
+ group: group,
+ active: true,
+ push_events: true,
+ url: 'http://update-jira.instance.com',
+ username: 'user',
+ password: 'secret'
+ )
+ end
+
+ let_it_be(:subgroup_integration) do
JiraService.create!(
- instance: true,
+ inherit_from_id: group_integration.id,
+ group: create(:group, parent: group),
active: true,
push_events: true,
url: 'http://update-jira.instance.com',
@@ -21,10 +35,9 @@ RSpec.describe BulkUpdateIntegrationService do
)
end
- let!(:integration) do
+ let_it_be(:integration) do
JiraService.create!(
project: create(:project),
- inherit_from_id: instance_integration.id,
instance: false,
active: true,
push_events: false,
@@ -36,21 +49,21 @@ RSpec.describe BulkUpdateIntegrationService do
context 'with inherited integration' do
it 'updates the integration' do
- described_class.new(instance_integration, Service.inherit_from_id(instance_integration.id)).execute
+ described_class.new(subgroup_integration, Service.where.not(project: nil)).execute
- expect(integration.reload.inherit_from_id).to eq(instance_integration.id)
+ expect(integration.reload.inherit_from_id).to eq(group_integration.id)
expect(integration.attributes.except(*excluded_attributes))
- .to eq(instance_integration.attributes.except(*excluded_attributes))
+ .to eq(subgroup_integration.attributes.except(*excluded_attributes))
end
context 'with integration with data fields' do
let(:excluded_attributes) { %w[id service_id created_at updated_at] }
it 'updates the data fields from the integration' do
- described_class.new(instance_integration, Service.inherit_from_id(instance_integration.id)).execute
+ described_class.new(subgroup_integration, Service.where.not(project: nil)).execute
expect(integration.reload.data_fields.attributes.except(*excluded_attributes))
- .to eq(instance_integration.data_fields.attributes.except(*excluded_attributes))
+ .to eq(subgroup_integration.data_fields.attributes.except(*excluded_attributes))
end
end
end
diff --git a/spec/services/ci/test_cases_service_spec.rb b/spec/services/ci/test_cases_service_spec.rb
new file mode 100644
index 00000000000..b61d308640f
--- /dev/null
+++ b/spec/services/ci/test_cases_service_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TestCasesService, :aggregate_failures do
+ describe '#execute' do
+ subject(:execute_service) { described_class.new.execute(build) }
+
+ context 'when build has test reports' do
+ let(:build) { create(:ci_build, :success, :test_reports) } # The test report has 2 test case failures
+
+ it 'creates test case failures records' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(2)
+ expect(Ci::TestCaseFailure.count).to eq(2)
+ end
+
+ context 'when feature flag for test failure history is disabled' do
+ before do
+ stub_feature_flags(test_failure_history: false)
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+
+ context 'when build is not for the default branch' do
+ before do
+ build.update_column(:ref, 'new-feature')
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+
+ context 'when test failure data have already been persisted with the same exact attributes' do
+ before do
+ execute_service
+ end
+
+ it 'does not fail but does not persist new data' do
+ expect { described_class.new.execute(build) }.not_to raise_error
+
+ expect(Ci::TestCase.count).to eq(2)
+ expect(Ci::TestCaseFailure.count).to eq(2)
+ end
+ end
+
+ context 'when test failure data have duplicates within the same payload (happens when the JUnit report has duplicate test case names but have different failures)' do
+ let(:build) { create(:ci_build, :success, :test_reports_with_duplicate_failed_test_names) } # The test report has 2 test case failures but with the same test case keys
+
+ it 'does not fail but does not persist duplicate data' do
+ expect { described_class.new.execute(build) }.not_to raise_error
+
+ expect(Ci::TestCase.count).to eq(1)
+ expect(Ci::TestCaseFailure.count).to eq(1)
+ end
+ end
+
+ context 'when number of failed test cases exceed the limit' do
+ before do
+ stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 1)
+ end
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+ end
+
+ context 'when build has no test reports' do
+ let(:build) { create(:ci_build, :running) }
+
+ it 'does not persist data' do
+ execute_service
+
+ expect(Ci::TestCase.count).to eq(0)
+ expect(Ci::TestCaseFailure.count).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/table_schema_helpers.rb b/spec/support/helpers/table_schema_helpers.rb
index 39f783caa0a..b79ea4eb5f0 100644
--- a/spec/support/helpers/table_schema_helpers.rb
+++ b/spec/support/helpers/table_schema_helpers.rb
@@ -43,9 +43,9 @@ module TableSchemaHelpers
sequence.relname as name
FROM pg_catalog.pg_class as sequence
INNER JOIN pg_catalog.pg_depend depend
- ON depend.objid = sequence.relfilenode
+ ON depend.objid = sequence.oid
INNER JOIN pg_catalog.pg_class class
- ON class.relfilenode = depend.refobjid
+ ON class.oid = depend.refobjid
INNER JOIN pg_catalog.pg_attribute attribute
ON attribute.attnum = depend.refobjsubid
AND attribute.attrelid = depend.refobjid
diff --git a/spec/support/shared_examples/controllers/trackable_shared_examples.rb b/spec/support/shared_examples/controllers/trackable_shared_examples.rb
index e82c27c43f5..8b616d8c152 100644
--- a/spec/support/shared_examples/controllers/trackable_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/trackable_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'a Trackable Controller' do
end
it 'tracks the action name' do
- expect(Gitlab::Tracking).to receive(:event).with('AnonymousController', 'index', {})
+ expect(Gitlab::Tracking).to receive(:event).with('AnonymousController', 'index')
get :index
end
end
diff --git a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb
new file mode 100644
index 00000000000..02e50b789cc
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'fetches labels' do
+ it 'returns correct labels' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/label'))
+ expect(json_response.size).to eq(expected_labels.size)
+ expect(json_response.map {|r| r['name'] }).to match_array(expected_labels)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index d730ed53109..ba31e8e6056 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -129,7 +129,7 @@ end
RSpec.shared_examples 'a package tracking event' do |category, action|
it "creates a gitlab tracking event #{action}" do
- expect(Gitlab::Tracking).to receive(:event).with(category, action, {})
+ expect(Gitlab::Tracking).to receive(:event).with(category, action)
expect { subject }.to change { Packages::Event.count }.by(1)
end
diff --git a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb
index 2e6feae3f98..2bc47c3e1c1 100644
--- a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'a gitlab tracking event' do |category, action|
it "creates a gitlab tracking event #{action}" do
- expect(Gitlab::Tracking).to receive(:event).with(category, action, {})
+ expect(Gitlab::Tracking).to receive(:event).with(category, action)
subject
end
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index 11b50961e9e..b0058c76e27 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -11,18 +11,28 @@ RSpec.describe BuildFinishedWorker do
context 'when build exists' do
let!(:build) { create(:ci_build) }
- it 'calculates coverage and calls hooks' do
- expect(BuildTraceSectionsWorker)
- .to receive(:new).ordered.and_call_original
- expect(BuildCoverageWorker)
- .to receive(:new).ordered.and_call_original
-
- expect_any_instance_of(BuildTraceSectionsWorker).to receive(:perform)
- expect_any_instance_of(BuildCoverageWorker).to receive(:perform)
+ it 'calculates coverage and calls hooks', :aggregate_failures do
+ trace_worker = double('trace worker')
+ coverage_worker = double('coverage worker')
+
+ allow(BuildTraceSectionsWorker).to receive(:new).and_return(trace_worker)
+ allow(BuildCoverageWorker).to receive(:new).and_return(coverage_worker)
+
+ # Unfortunately, `ordered` does not seem to work when called within `allow_next_instance_of`
+ # so we're doing this the long and dirty way
+ expect(trace_worker).to receive(:perform).ordered
+ expect(coverage_worker).to receive(:perform).ordered
+
+ expect_next_instance_of(Ci::BuildReportResultWorker) do |instance|
+ expect(instance).to receive(:perform)
+ end
+ expect_next_instance_of(Ci::TestCasesService) do |instance|
+ expect(instance).to receive(:execute)
+ end
+
expect(BuildHooksWorker).to receive(:perform_async)
expect(ExpirePipelineCacheWorker).to receive(:perform_async)
expect(ChatNotificationWorker).not_to receive(:perform_async)
- expect(Ci::BuildReportResultWorker).not_to receive(:perform)
expect(ArchiveTraceWorker).to receive(:perform_in)
subject
@@ -31,7 +41,7 @@ RSpec.describe BuildFinishedWorker do
context 'when build does not exist' do
it 'does not raise exception' do
- expect { described_class.new.perform(123) }
+ expect { described_class.new.perform(non_existing_record_id) }
.not_to raise_error
end
end
@@ -45,17 +55,5 @@ RSpec.describe BuildFinishedWorker do
subject
end
end
-
- context 'when build has a test report' do
- let(:build) { create(:ci_build, :test_reports) }
-
- it 'schedules a BuildReportResult job' do
- expect_next_instance_of(Ci::BuildReportResultWorker) do |worker|
- expect(worker).to receive(:perform).with(build.id)
- end
-
- subject
- end
- end
end
end
diff --git a/spec/workers/propagate_integration_inherit_descendant_worker_spec.rb b/spec/workers/propagate_integration_inherit_descendant_worker_spec.rb
new file mode 100644
index 00000000000..b5eb0f69017
--- /dev/null
+++ b/spec/workers/propagate_integration_inherit_descendant_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe PropagateIntegrationInheritDescendantWorker do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:group_integration) { create(:redmine_service, group: group, project: nil) }
+ let_it_be(:subgroup_integration) { create(:redmine_service, group: subgroup, project: nil, inherit_from_id: group_integration.id) }
+
+ it_behaves_like 'an idempotent worker' do
+ let(:job_args) { [group_integration.id, subgroup_integration.id, subgroup_integration.id] }
+
+ it 'calls to BulkUpdateIntegrationService' do
+ expect(BulkUpdateIntegrationService).to receive(:new)
+ .with(group_integration, match_array(subgroup_integration)).twice
+ .and_return(double(execute: nil))
+
+ subject
+ end
+ end
+
+ context 'with an invalid integration id' do
+ it 'returns without failure' do
+ expect(BulkUpdateIntegrationService).not_to receive(:new)
+
+ subject.perform(0, subgroup_integration.id, subgroup_integration.id)
+ end
+ end
+end
diff --git a/spec/workers/propagate_integration_inherit_worker_spec.rb b/spec/workers/propagate_integration_inherit_worker_spec.rb
index cbfee29a6a0..39219eaa3b5 100644
--- a/spec/workers/propagate_integration_inherit_worker_spec.rb
+++ b/spec/workers/propagate_integration_inherit_worker_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe PropagateIntegrationInheritWorker do
it_behaves_like 'an idempotent worker' do
let(:job_args) { [integration.id, integration1.id, integration3.id] }
- it 'calls to BulkCreateIntegrationService' do
+ it 'calls to BulkUpdateIntegrationService' do
expect(BulkUpdateIntegrationService).to receive(:new)
.with(integration, match_array(integration1)).twice
.and_return(double(execute: nil))