diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-23 18:08:31 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-23 18:08:31 +0000 |
commit | 7e1e5ca371cbfe77bdf56fcf65a4e749e6e86a06 (patch) | |
tree | c40953fc74404b5d86bb2ef72bab89526c5ca7d6 | |
parent | 9086e66ee72527839053ec6db19ed321a3b3a61b (diff) | |
download | gitlab-ce-7e1e5ca371cbfe77bdf56fcf65a4e749e6e86a06.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 Binary files differnew file mode 100644 index 00000000000..9bcd06759da --- /dev/null +++ b/spec/fixtures/junit/junit_with_duplicate_failed_test_names.xml.gz 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)) |