Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml11
-rw-r--r--app/graphql/resolvers/project_jobs_resolver.rb1
-rw-r--r--app/graphql/resolvers/project_pipeline_resolver.rb7
-rw-r--r--app/graphql/resolvers/project_pipelines_resolver.rb2
-rw-r--r--app/graphql/types/ci/job_type.rb22
-rw-r--r--app/graphql/types/ci/stage_type.rb5
-rw-r--r--app/models/ci/stage.rb1
-rw-r--r--app/models/namespaces/traversal/linear.rb36
-rw-r--r--app/models/namespaces/traversal/recursive.rb1
-rw-r--r--config/feature_flags/development/use_traversal_ids_for_ancestors_upto.yml (renamed from config/feature_flags/development/ci_build_tags_limit.yml)12
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/ci/ci_cd_for_external_repos/index.md15
-rw-r--r--doc/development/documentation/site_architecture/deployment_process.md8
-rw-r--r--doc/user/project/members/index.md25
-rw-r--r--doc/user/workspace/index.md8
-rw-r--r--lib/gitlab/ci/config/entry/tags.rb2
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/tags_spec.rb22
-rw-r--r--spec/models/ci/stage_spec.rb12
-rw-r--r--spec/models/concerns/group_descendant_spec.rb6
-rw-r--r--spec/models/group_spec.rb8
-rw-r--r--spec/models/namespace_spec.rb46
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb109
-rw-r--r--spec/requests/api/graphql/project/jobs_spec.rb56
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb42
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb52
26 files changed, 403 insertions, 108 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index b9e18d3f15e..0b4f7ca1dd9 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -40,6 +40,9 @@
.if-automated-merge-request: &if-automated-merge-request
if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "release-tools/update-gitaly" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /stable-ee$/'
+.if-merge-request-targeting-stable-branch: &if-merge-request-targeting-stable-branch
+ if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/'
+
.if-merge-request-labels-as-if-foss: &if-merge-request-labels-as-if-foss
if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-as-if-foss/'
@@ -576,6 +579,8 @@
when: never
- <<: *if-security-merge-request
when: never
+ - <<: *if-merge-request-targeting-stable-branch
+ when: never
- <<: *if-merge-request-labels-as-if-jh
- <<: *if-merge-request-labels-run-all-rspec
- changes: *code-backstage-qa-patterns
@@ -609,6 +614,8 @@
when: never
- <<: *if-security-merge-request
when: never
+ - <<: *if-merge-request-targeting-stable-branch
+ when: never
- <<: *if-merge-request-labels-as-if-jh
- <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request
@@ -1250,6 +1257,8 @@
when: never
- <<: *if-security-merge-request
when: never
+ - <<: *if-merge-request-targeting-stable-branch
+ when: never
- <<: *if-merge-request-labels-as-if-jh
allow_failure: true
- <<: *if-merge-request
@@ -1693,6 +1702,8 @@
when: never
- <<: *if-security-merge-request
when: never
+ - <<: *if-merge-request-targeting-stable-branch
+ when: never
- <<: *if-merge-request-labels-as-if-jh
- <<: *if-merge-request-labels-run-all-rspec
- changes: *code-backstage-qa-patterns
diff --git a/app/graphql/resolvers/project_jobs_resolver.rb b/app/graphql/resolvers/project_jobs_resolver.rb
index 75068014242..8a2693ee46b 100644
--- a/app/graphql/resolvers/project_jobs_resolver.rb
+++ b/app/graphql/resolvers/project_jobs_resolver.rb
@@ -33,6 +33,7 @@ module Resolvers
def preloads
{
+ previous_stage_jobs_and_needs: [:needs, :pipeline],
artifacts: [:job_artifacts],
pipeline: [:user]
}
diff --git a/app/graphql/resolvers/project_pipeline_resolver.rb b/app/graphql/resolvers/project_pipeline_resolver.rb
index 5acd7f95606..ea733ab08ad 100644
--- a/app/graphql/resolvers/project_pipeline_resolver.rb
+++ b/app/graphql/resolvers/project_pipeline_resolver.rb
@@ -24,7 +24,6 @@ module Resolvers
super
end
- # the preloads are defined on ee/app/graphql/ee/resolvers/project_pipeline_resolver.rb
def resolve(iid: nil, sha: nil, **args)
self.lookahead = args.delete(:lookahead)
@@ -42,5 +41,11 @@ module Resolvers
end
end
end
+
+ def unconditional_includes
+ [
+ { statuses: [:needs] }
+ ]
+ end
end
end
diff --git a/app/graphql/resolvers/project_pipelines_resolver.rb b/app/graphql/resolvers/project_pipelines_resolver.rb
index 5a1e92efc96..23423b9274a 100644
--- a/app/graphql/resolvers/project_pipelines_resolver.rb
+++ b/app/graphql/resolvers/project_pipelines_resolver.rb
@@ -18,7 +18,7 @@ module Resolvers
def preloads
{
- jobs: [:statuses],
+ jobs: { statuses: [:needs] },
upstream: [:triggered_by_pipeline],
downstream: [:triggered_pipelines]
}
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index 48bd91bfc5b..57ff35bcaa5 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -50,6 +50,8 @@ module Types
null: true,
description: 'How long the job was enqueued before starting.'
+ field :previous_stage_jobs_and_needs, Types::Ci::JobType.connection_type, null: true,
+ description: 'All prerequisite jobs.'
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
description: 'Detailed status of the job.'
field :artifacts, Types::Ci::JobArtifactType.connection_type, null: true,
@@ -101,6 +103,26 @@ module Types
end
end
+ def previous_stage_jobs_and_needs
+ Gitlab::Graphql::Lazy.with_value(previous_stage_jobs) do |jobs|
+ (jobs + object.needs).uniq(&:name)
+ end
+ end
+
+ def previous_stage_jobs
+ BatchLoader::GraphQL.for([object.pipeline, object.stage_idx - 1]).batch(default_value: []) do |tuples, loader|
+ tuples.group_by(&:first).each do |pipeline, keys|
+ positions = keys.map(&:second)
+
+ stages = pipeline.stages.by_position(positions)
+
+ stages.each do |stage|
+ loader.call([pipeline, stage.position], stage.latest_statuses)
+ end
+ end
+ end
+ end
+
def stage
::Gitlab::Graphql::Lazy.with_value(pipeline) do |pl|
BatchLoader::GraphQL.for([pl, object.stage]).batch do |ids, loader|
diff --git a/app/graphql/types/ci/stage_type.rb b/app/graphql/types/ci/stage_type.rb
index c0d931b3d31..fc887c83da3 100644
--- a/app/graphql/types/ci/stage_type.rb
+++ b/app/graphql/types/ci/stage_type.rb
@@ -31,7 +31,10 @@ module Types
BatchLoader::GraphQL.for(key).batch(default_value: []) do |keys, loader|
by_pipeline = keys.group_by(&:pipeline)
- include_needs = keys.any? { |k| k.requires?(%i[nodes jobs nodes needs]) }
+ include_needs = keys.any? do |k|
+ k.requires?(%i[nodes jobs nodes needs]) ||
+ k.requires?(%i[nodes jobs nodes previousStageJobsAndNeeds])
+ end
by_pipeline.each do |pl, key_group|
project = pl.project
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index e2b15497638..8c4e97ac840 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -22,6 +22,7 @@ module Ci
scope :ordered, -> { order(position: :asc) }
scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) }
scope :by_name, ->(names) { where(name: names) }
+ scope :by_position, ->(positions) { where(position: positions) }
with_options unless: :importing? do
validates :project, presence: true
diff --git a/app/models/namespaces/traversal/linear.rb b/app/models/namespaces/traversal/linear.rb
index 1736fe82ca5..36b45dde6b4 100644
--- a/app/models/namespaces/traversal/linear.rb
+++ b/app/models/namespaces/traversal/linear.rb
@@ -64,6 +64,13 @@ module Namespaces
traversal_ids.present?
end
+ def use_traversal_ids_for_ancestors_upto?
+ return false unless use_traversal_ids?
+ return false unless Feature.enabled?(:use_traversal_ids_for_ancestors_upto, root_ancestor, default_enabled: :yaml)
+
+ traversal_ids.present?
+ end
+
def use_traversal_ids_for_root_ancestor?
return false unless Feature.enabled?(:use_traversal_ids_for_root_ancestor, default_enabled: :yaml)
@@ -114,6 +121,35 @@ module Namespaces
hierarchy_order == :desc ? traversal_ids[0..-2] : traversal_ids[0..-2].reverse
end
+ # Returns all ancestors upto but excluding the top.
+ # When no top is given, all ancestors are returned.
+ # When top is not found, returns all ancestors.
+ #
+ # This copies the behavior of the recursive method. We will deprecate
+ # this behavior soon.
+ def ancestors_upto(top = nil, hierarchy_order: nil)
+ return super unless use_traversal_ids_for_ancestors_upto?
+
+ # We can't use a default value in the method definition above because
+ # we need to preserve those specific parameters for super.
+ hierarchy_order ||= :desc
+
+ # Get all ancestor IDs inclusively between top and our parent.
+ top_index = top ? traversal_ids.find_index(top.id) : 0
+ ids = traversal_ids[top_index...-1]
+ ids_string = ids.map { |id| Integer(id) }.join(',')
+
+ # WITH ORDINALITY lets us order the result to match traversal_ids order.
+ from_sql = <<~SQL
+ unnest(ARRAY[#{ids_string}]::bigint[]) WITH ORDINALITY AS ancestors(id, ord)
+ INNER JOIN namespaces ON namespaces.id = ancestors.id
+ SQL
+
+ self.class
+ .from(Arel.sql(from_sql))
+ .order('ancestors.ord': hierarchy_order)
+ end
+
def self_and_ancestors(hierarchy_order: nil)
return super unless use_traversal_ids_for_ancestors?
diff --git a/app/models/namespaces/traversal/recursive.rb b/app/models/namespaces/traversal/recursive.rb
index 8d2c5d3be5a..53eac27aa54 100644
--- a/app/models/namespaces/traversal/recursive.rb
+++ b/app/models/namespaces/traversal/recursive.rb
@@ -46,6 +46,7 @@ module Namespaces
object_hierarchy(self.class.where(id: id))
.ancestors(upto: top, hierarchy_order: hierarchy_order)
end
+ alias_method :recursive_ancestors_upto, :ancestors_upto
def self_and_ancestors(hierarchy_order: nil)
return self.class.where(id: id) unless parent_id
diff --git a/config/feature_flags/development/ci_build_tags_limit.yml b/config/feature_flags/development/use_traversal_ids_for_ancestors_upto.yml
index 8aaa03a87e3..4fe7ca695c3 100644
--- a/config/feature_flags/development/ci_build_tags_limit.yml
+++ b/config/feature_flags/development/use_traversal_ids_for_ancestors_upto.yml
@@ -1,8 +1,8 @@
---
-name: ci_build_tags_limit
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68380
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338929
-milestone: '14.2'
+name: use_traversal_ids_for_ancestors_upto
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72662
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343619
+milestone: '14.6'
type: development
-group: group::pipeline execution
-default_enabled: true
+group: group::access
+default_enabled: false
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index b86ffbe0d84..0400dc31128 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -8663,6 +8663,7 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cijobneeds"></a>`needs` | [`CiBuildNeedConnection`](#cibuildneedconnection) | References to builds that must complete before the jobs run. (see [Connections](#connections)) |
| <a id="cijobpipeline"></a>`pipeline` | [`Pipeline`](#pipeline) | Pipeline the job belongs to. |
| <a id="cijobplayable"></a>`playable` | [`Boolean!`](#boolean) | Indicates the job can be played. |
+| <a id="cijobpreviousstagejobsandneeds"></a>`previousStageJobsAndNeeds` | [`CiJobConnection`](#cijobconnection) | All prerequisite jobs. (see [Connections](#connections)) |
| <a id="cijobqueuedat"></a>`queuedAt` | [`Time`](#time) | When the job was enqueued and marked as pending. |
| <a id="cijobqueuedduration"></a>`queuedDuration` | [`Duration`](#duration) | How long the job was enqueued before starting. |
| <a id="cijobrefname"></a>`refName` | [`String`](#string) | Ref name of the job. |
diff --git a/doc/ci/ci_cd_for_external_repos/index.md b/doc/ci/ci_cd_for_external_repos/index.md
index fbfcdcbf64f..1d6b76da5b9 100644
--- a/doc/ci/ci_cd_for_external_repos/index.md
+++ b/doc/ci/ci_cd_for_external_repos/index.md
@@ -9,33 +9,30 @@ type: index, howto
>[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4642) in GitLab 10.6.
-GitLab CI/CD can be used with:
-
-- [GitHub](github_integration.md).
-- [Bitbucket Cloud](bitbucket_integration.md).
-- Any other Git server.
+GitLab CI/CD can be used with [GitHub](github_integration.md), [Bitbucket Cloud](bitbucket_integration.md),
+or any other Git server.
Instead of moving your entire project to GitLab, you can connect your
external repository to get the benefits of GitLab CI/CD.
Connecting an external repository sets up [repository mirroring](../../user/project/repository/mirror/index.md)
-and create a lightweight project with issues, merge requests, wiki, and
+and creates a lightweight project with issues, merge requests, wiki, and
snippets disabled. These features
[can be re-enabled later](../../user/project/settings/index.md#sharing-and-permissions).
+## Connect to an external repository
+
To connect to an external repository:
<!-- vale gitlab.Spelling = NO -->
-1. On the top menu, select **Projects > Create new project**.
+1. On the top bar, select **Menu > Projects > Create new project**.
1. Select **Run CI/CD for external repository**.
1. Select **GitHub** or **Repo by URL**.
1. Complete the fields.
<!-- vale gitlab.Spelling = YES -->
-![CI/CD for external repository project creation](img/ci_cd_for_external_repo.png)
-
## Pipelines for external pull requests
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/65139) in GitLab 12.3.
diff --git a/doc/development/documentation/site_architecture/deployment_process.md b/doc/development/documentation/site_architecture/deployment_process.md
index c038ee96dbf..25bc699c9d4 100644
--- a/doc/development/documentation/site_architecture/deployment_process.md
+++ b/doc/development/documentation/site_architecture/deployment_process.md
@@ -50,8 +50,8 @@ that stable documentation and deploys it to the registry. For example:
- [13.12 merge request pipeline](https://gitlab.com/gitlab-org/gitlab-docs/-/pipelines/395365202).
- [12.10 merge request pipeline](https://gitlab.com/gitlab-org/gitlab-docs/-/pipelines/395365405).
-In particular, the [`image:docs-single` job](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/4c18963fe0a414ad62f55b9e18f922588b2dd155/.gitlab-ci.yml#L655) in each pipeline
-takes what is built, and pushes it to the [container registry](https://gitlab.com/gitlab-org/gitlab-docs/container_registry/631635).
+In particular, the [`image:docs-single` job](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/4c18963fe0a414ad62f55b9e18f922588b2dd155/.gitlab-ci.yml#L655) in each pipeline runs automatically.
+It takes what is built, and pushes it to the [container registry](https://gitlab.com/gitlab-org/gitlab-docs/container_registry/631635).
```mermaid
graph TD
@@ -91,6 +91,7 @@ The [`image:docs-latest` job](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/4
- Pulls the latest documentation from the default branches of the relevant upstream projects.
- Pulls the Docker images previously built by the `image:docs-single` jobs.
+- Must be run manually on a scheduled pipeline.
For example, [a pipeline](https://gitlab.com/gitlab-org/gitlab-docs/-/pipelines/399233948) containing the
[`image:docs-latest` job](https://gitlab.com/gitlab-org/gitlab-docs/-/jobs/1733948330):
@@ -122,7 +123,8 @@ graph TD
for it must be deployed to become available.
The [`pages`](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/4c18963fe0a414ad62f55b9e18f922588b2dd155/.gitlab-ci.yml#L491)
-job runs the necessary commands to combine:
+job runs automatically when a pipeline runs on the default branch (`main`).
+It runs the necessary commands to combine:
- A very up-to-date build of the `gitlab-docs` site code.
- The latest docs from the default branches of the upstream projects.
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index adf0a115c6e..283576fb4e9 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -52,7 +52,8 @@ Prerequisite:
To add groups to a project:
-1. Go to your project and select **Project information > Members**.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Project information > Members**.
1. On the **Invite group** tab, under **Select a group to invite**, choose a group.
1. Select the highest max [role](../../permissions.md) for users in the group.
1. Optional. Choose an expiration date. On that date, the user can no longer access the project.
@@ -75,7 +76,8 @@ Prerequisite:
To import users:
-1. Go to your project and select **Project information > Members**.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Project information > Members**.
1. On the **Invite member** tab, at the bottom of the panel, select **Import**.
1. Select the project. You can view only the projects for which you're a maintainer.
1. Select **Import project members**.
@@ -115,7 +117,8 @@ Prerequisites:
To remove a member from a project:
-1. Go to your project and select **Project information > Members**.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Project information > Members**.
1. Next to the project member you want to remove, select **Remove member** **{remove}**.
1. Optional. In the confirmation box, select the
**Also unassign this user from related issues and merge requests** checkbox.
@@ -136,7 +139,8 @@ You can filter and sort members in a project.
### Display inherited members
-1. Go to your project and select **Project information > Members**.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Project information > Members**.
1. In the **Filter members** box, select `Membership` `=` `Inherited`.
1. Press Enter.
@@ -144,7 +148,8 @@ You can filter and sort members in a project.
### Display direct members
-1. Go to your project and select **Project information > Members**.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Project information > Members**.
1. In the **Filter members** box, select `Membership` `=` `Direct`.
1. Press Enter.
@@ -166,7 +171,7 @@ You can sort members by **Account**, **Access granted**, **Max role**, or **Last
GitLab users can request to become a member of a project.
-1. Go to the project you'd like to be a member of.
+1. On the top bar, select **Menu > Projects** and find the project you want to be a member of.
1. By the project name, select **Request Access**.
![Request access button](img/request_access_button.png)
@@ -189,8 +194,9 @@ Prerequisite:
- You must be the project owner.
-1. Go to the project and select **Settings > General**.
-1. Expand the **Visibility, project features, permissions** section.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Visibility, project features, permissions**.
1. Under **Project visibility**, select **Users can request access**.
1. Select **Save changes**.
@@ -213,7 +219,8 @@ This feature might not be available to you. Check the **version history** note a
In GitLab 13.11, you can optionally replace the form to add a member with a modal window.
To add a member after enabling this feature:
-1. Go to your project and select **Project information > Members**.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Project information > Members**.
1. Select **Invite members**.
1. Enter an email address and select a role.
1. Optional. Select an **Access expiration date**.
diff --git a/doc/user/workspace/index.md b/doc/user/workspace/index.md
index 8ca7f7defb2..cf35f082880 100644
--- a/doc/user/workspace/index.md
+++ b/doc/user/workspace/index.md
@@ -6,6 +6,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Workspace
+DISCLAIMER:
+This page contains information related to upcoming products, features, and functionality.
+It is important to note that the information presented is for informational purposes only.
+Please do not rely on this information for purchasing or planning purposes.
+As with all projects, the items mentioned on this page are subject to change or delay.
+The development, release, and timing of any products, features, or functionality remain at the
+sole discretion of GitLab Inc.
+
Workspace will be above the [top-level namespaces](../group/index.md#namespaces) for you to manage
everything you do as a GitLab administrator, including:
diff --git a/lib/gitlab/ci/config/entry/tags.rb b/lib/gitlab/ci/config/entry/tags.rb
index ca3b48372e2..6044cfddbdc 100644
--- a/lib/gitlab/ci/config/entry/tags.rb
+++ b/lib/gitlab/ci/config/entry/tags.rb
@@ -16,8 +16,6 @@ module Gitlab
validates :config, array_of_strings: true
validate do
- next unless ::Feature.enabled?(:ci_build_tags_limit, default_enabled: :yaml)
-
if config.is_a?(Array) && config.size >= TAGS_LIMIT
errors.add(:config, _("must be less than the limit of %{tag_limit} tags") % { tag_limit: TAGS_LIMIT })
end
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index e95a7da4fe5..0da2e291d3c 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -25,6 +25,7 @@ RSpec.describe Types::Ci::JobType do
needs
pipeline
playable
+ previousStageJobsAndNeeds
queued_at
queued_duration
refName
diff --git a/spec/lib/gitlab/ci/config/entry/tags_spec.rb b/spec/lib/gitlab/ci/config/entry/tags_spec.rb
index 79317de373b..e05d4ae52b2 100644
--- a/spec/lib/gitlab/ci/config/entry/tags_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/tags_spec.rb
@@ -36,25 +36,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Tags do
context 'when tags limit is reached' do
let(:config) { Array.new(50) {|i| "tag-#{i}" } }
- context 'when ci_build_tags_limit is enabled' do
- before do
- stub_feature_flags(ci_build_tags_limit: true)
- end
-
- it 'reports error' do
- expect(entry.errors)
- .to include "tags config must be less than the limit of #{described_class::TAGS_LIMIT} tags"
- end
- end
-
- context 'when ci_build_tags_limit is disabled' do
- before do
- stub_feature_flags(ci_build_tags_limit: false)
- end
-
- it 'does not report an error' do
- expect(entry.errors).to be_empty
- end
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "tags config must be less than the limit of #{described_class::TAGS_LIMIT} tags"
end
end
end
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index 5e0fcb4882f..2b6f22e68f1 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -28,6 +28,18 @@ RSpec.describe Ci::Stage, :models do
end
end
+ describe '.by_position' do
+ it 'finds stages by position' do
+ a = create(:ci_stage_entity, position: 1)
+ b = create(:ci_stage_entity, position: 2)
+ c = create(:ci_stage_entity, position: 3)
+
+ expect(described_class.by_position(1)).to contain_exactly(a)
+ expect(described_class.by_position(2)).to contain_exactly(b)
+ expect(described_class.by_position(%w[1 3])).to contain_exactly(a, c)
+ end
+ end
+
describe '.by_name' do
it 'finds stages by name' do
a = create(:ci_stage_entity, name: 'a')
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index b29fa910ee6..d593d829dca 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -19,14 +19,16 @@ RSpec.describe GroupDescendant do
query_count = ActiveRecord::QueryRecorder.new { test_group.hierarchy }.count
- expect(query_count).to eq(1)
+ # use_traversal_ids_for_ancestors_upto actor based feature flag check adds an extra query.
+ expect(query_count).to eq(2)
end
it 'only queries once for the ancestors when a top is given' do
test_group = create(:group, parent: subsub_group).reload
recorder = ActiveRecord::QueryRecorder.new { test_group.hierarchy(subgroup) }
- expect(recorder.count).to eq(1)
+ # use_traversal_ids_for_ancestors_upto actor based feature flag check adds an extra query.
+ expect(recorder.count).to eq(2)
end
it 'builds a hierarchy for a group' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 735aa4df2ba..6a2f9b3fbc0 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -533,6 +533,10 @@ RSpec.describe Group do
describe '#ancestors' do
it { expect(group.ancestors.to_sql).not_to include 'traversal_ids <@' }
end
+
+ describe '#ancestors_upto' do
+ it { expect(group.ancestors_upto.to_sql).not_to include "WITH ORDINALITY" }
+ end
end
context 'linear' do
@@ -566,6 +570,10 @@ RSpec.describe Group do
end
end
+ describe '#ancestors_upto' do
+ it { expect(group.ancestors_upto.to_sql).to include "WITH ORDINALITY" }
+ end
+
context 'when project namespace exists in the group' do
let!(:project) { create(:project, group: group) }
let!(:project_namespace) { project.project_namespace }
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 8f5860c799c..5adbcd0fa52 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -700,20 +700,6 @@ RSpec.describe Namespace do
end
end
- describe '#ancestors_upto' do
- let(:parent) { create(:group) }
- let(:child) { create(:group, parent: parent) }
- let(:child2) { create(:group, parent: child) }
-
- it 'returns all ancestors when no namespace is given' do
- expect(child2.ancestors_upto).to contain_exactly(child, parent)
- end
-
- it 'includes ancestors upto but excluding the given ancestor' do
- expect(child2.ancestors_upto(parent)).to contain_exactly(child)
- end
- end
-
describe '#move_dir', :request_store do
shared_examples "namespace restrictions" do
context "when any project has container images" do
@@ -1274,6 +1260,38 @@ RSpec.describe Namespace do
end
end
+ describe '#use_traversal_ids_for_ancestors_upto?' do
+ let_it_be(:namespace, reload: true) { create(:namespace) }
+
+ subject { namespace.use_traversal_ids_for_ancestors_upto? }
+
+ context 'when use_traversal_ids_for_ancestors_upto feature flag is true' do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestors_upto: true)
+ end
+
+ it { is_expected.to eq true }
+
+ it_behaves_like 'disabled feature flag when traversal_ids is blank'
+ end
+
+ context 'when use_traversal_ids_for_ancestors_upto feature flag is false' do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestors_upto: false)
+ end
+
+ it { is_expected.to eq false }
+ end
+
+ context 'when use_traversal_ids? feature flag is false' do
+ before do
+ stub_feature_flags(use_traversal_ids: false)
+ end
+
+ it { is_expected.to eq false }
+ end
+ end
+
describe '#users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index e6362fdde88..f268036a78a 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Query.project.pipeline' do
describe '.stages.groups.jobs' do
let(:pipeline) do
pipeline = create(:ci_pipeline, project: project, user: user)
- stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first')
+ stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first', position: 1)
create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'my test job')
pipeline
@@ -44,13 +44,18 @@ RSpec.describe 'Query.project.pipeline' do
name
jobs {
nodes {
- detailedStatus {
- id
- }
name
needs {
nodes { #{all_graphql_fields_for('CiBuildNeed')} }
}
+ previousStageJobsAndNeeds {
+ nodes {
+ name
+ }
+ }
+ detailedStatus {
+ id
+ }
pipeline {
id
}
@@ -62,58 +67,80 @@ RSpec.describe 'Query.project.pipeline' do
FIELDS
end
- context 'when there are build needs' do
- before do
- pipeline.statuses.each do |build|
- create_list(:ci_build_need, 2, build: build)
- end
- end
-
- it 'reports the build needs' do
- post_graphql(query, current_user: user)
-
- expect(jobs_graphql_data).to contain_exactly a_hash_including(
- 'needs' => a_hash_including(
- 'nodes' => contain_exactly(
- a_hash_including('name' => String),
- a_hash_including('name' => String)
- )
- )
- )
- end
- end
-
it 'returns the jobs of a pipeline stage' do
post_graphql(query, current_user: user)
expect(jobs_graphql_data).to contain_exactly(a_hash_including('name' => 'my test job'))
end
- describe 'performance' do
+ context 'when there is more than one stage and job needs' do
before do
build_stage = create(:ci_stage_entity, position: 2, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage_entity, position: 3, name: 'test', project: project, pipeline: pipeline)
- create(:commit_status, pipeline: pipeline, stage_id: build_stage.id, name: 'docker 1 2')
- create(:commit_status, pipeline: pipeline, stage_id: build_stage.id, name: 'docker 2 2')
- create(:commit_status, pipeline: pipeline, stage_id: test_stage.id, name: 'rspec 1 2')
- create(:commit_status, pipeline: pipeline, stage_id: test_stage.id, name: 'rspec 2 2')
- end
+ deploy_stage = create(:ci_stage_entity, position: 4, name: 'deploy', project: project, pipeline: pipeline)
- it 'can find the first stage' do
- post_graphql(query, current_user: user, variables: first_n.with(1))
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)
+ test_job = create(:ci_bridge, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 2 2', stage: test_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: deploy_stage.position, name: 'deploy 1 2', stage: deploy_stage)
+ deploy_job = create(:ci_build, pipeline: pipeline, stage_idx: deploy_stage.position, name: 'deploy 2 2', stage: deploy_stage)
- expect(jobs_graphql_data).to contain_exactly(a_hash_including('name' => 'my test job'))
+ create(:ci_build_need, build: test_job, name: 'my test job')
+ create(:ci_build_need, build: deploy_job, name: 'rspec 1 2')
end
- it 'can find all stages' do
- post_graphql(query, current_user: user, variables: first_n.with(3))
+ it 'reports the build needs and previous stages with no duplicates' do
+ post_graphql(query, current_user: user)
expect(jobs_graphql_data).to contain_exactly(
- a_hash_including('name' => 'my test job'),
- a_hash_including('name' => 'docker 1 2'),
- a_hash_including('name' => 'docker 2 2'),
- a_hash_including('name' => 'rspec 1 2'),
- a_hash_including('name' => 'rspec 2 2')
+ a_hash_including(
+ 'name' => 'my test job',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsAndNeeds' => { 'nodes' => [] }
+ ),
+ a_hash_including(
+ 'name' => 'docker 1 2',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsAndNeeds' => { 'nodes' => [
+ { "name" => "my test job" }
+ ] }
+ ),
+ a_hash_including(
+ 'name' => 'docker 2 2',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsAndNeeds' => { 'nodes' => [
+ { "name" => "my test job" }
+ ] }
+ ),
+ a_hash_including(
+ 'name' => 'rspec 1 2',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsAndNeeds' => { 'nodes' => [
+ { "name" => "docker 1 2" }, { "name" => "docker 2 2" }
+ ] }
+ ),
+ a_hash_including(
+ 'name' => 'rspec 2 2',
+ 'needs' => { 'nodes' => [a_hash_including('name' => 'my test job')] },
+ 'previousStageJobsAndNeeds' => { 'nodes' => [
+ { "name" => "docker 1 2" }, { "name" => "docker 2 2" }, { "name" => "my test job" }
+ ] }
+ ),
+ a_hash_including(
+ 'name' => 'deploy 1 2',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsAndNeeds' => { 'nodes' => [
+ { "name" => "rspec 1 2" }, { "name" => "rspec 2 2" }
+ ] }
+ ),
+ a_hash_including(
+ 'name' => 'deploy 2 2',
+ 'needs' => { 'nodes' => [a_hash_including('name' => 'rspec 1 2')] },
+ 'previousStageJobsAndNeeds' => { 'nodes' => [
+ { "name" => "rspec 1 2" }, { "name" => "rspec 2 2" }
+ ] }
+ )
)
end
diff --git a/spec/requests/api/graphql/project/jobs_spec.rb b/spec/requests/api/graphql/project/jobs_spec.rb
new file mode 100644
index 00000000000..1a823ede9ac
--- /dev/null
+++ b/spec/requests/api/graphql/project/jobs_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'Query.project.jobs' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:user) { create(:user) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, user: user)
+ end
+
+ let(:query) do
+ <<~QUERY
+ {
+ project(fullPath: "#{project.full_path}") {
+ jobs {
+ nodes {
+ name
+ previousStageJobsAndNeeds {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ QUERY
+ end
+
+ it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
+ build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
+ test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)
+ test_job = create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 2 2', stage: test_stage)
+ create(:ci_build_need, build: test_job, name: 'docker 1 2')
+
+ post_graphql(query, current_user: user)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post_graphql(query, current_user: user)
+ end
+
+ create(:ci_build, name: 'test-a', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ test_b_job = create(:ci_build, name: 'test-b', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ create(:ci_build_need, build: test_b_job, name: 'docker 2 2')
+
+ expect do
+ post_graphql(query, current_user: user)
+ end.not_to exceed_all_query_limit(control)
+ end
+end
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index d46ef313563..73e02e2a4b1 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -273,6 +273,48 @@ RSpec.describe 'getting pipeline information nested in a project' do
end
end
+ context 'N+1 queries on pipeline jobs' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:fields) do
+ <<~FIELDS
+ jobs {
+ nodes {
+ previousStageJobsAndNeeds {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ FIELDS
+ end
+
+ it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
+ build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
+ test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)
+ test_job = create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 2 2', stage: test_stage)
+ create(:ci_build_need, build: test_job, name: 'docker 1 2')
+
+ post_graphql(query, current_user: current_user)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post_graphql(query, current_user: current_user)
+ end
+
+ create(:ci_build, name: 'test-a', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ test_b_job = create(:ci_build, name: 'test-b', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ create(:ci_build_need, build: test_b_job, name: 'docker 2 2')
+
+ expect do
+ post_graphql(query, current_user: current_user)
+ end.not_to exceed_all_query_limit(control)
+ end
+ end
+
context 'N+1 queries on stages jobs' do
let(:depth) { 5 }
let(:fields) do
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index ac6a843663f..73e22b97abc 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -205,6 +205,58 @@ RSpec.shared_examples 'namespace traversal' do
end
end
+ shared_examples '#ancestors_upto' do
+ let(:parent) { create(:group) }
+ let(:child) { create(:group, parent: parent) }
+ let(:child2) { create(:group, parent: child) }
+
+ it 'returns all ancestors when no namespace is given' do
+ expect(child2.ancestors_upto).to contain_exactly(child, parent)
+ end
+
+ it 'includes ancestors upto but excluding the given ancestor' do
+ expect(child2.ancestors_upto(parent)).to contain_exactly(child)
+ end
+
+ context 'with asc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(child2.ancestors_upto(hierarchy_order: :asc)).to eq([child, parent])
+ end
+ end
+
+ context 'with desc hierarchy_order' do
+ it 'returns the correct ancestor ids' do
+ expect(child2.ancestors_upto(hierarchy_order: :desc)).to eq([parent, child])
+ end
+ end
+
+ describe '#recursive_self_and_ancestor_ids' do
+ it 'is equivalent to ancestors_upto' do
+ recursive_result = child2.recursive_ancestors_upto(parent)
+ linear_result = child2.ancestors_upto(parent)
+ expect(linear_result).to match_array recursive_result
+ end
+
+ it 'makes a recursive query' do
+ expect { child2.recursive_ancestors_upto.try(:load) }.to make_queries_matching(/WITH RECURSIVE/)
+ end
+ end
+ end
+
+ describe '#ancestors_upto' do
+ context 'with use_traversal_ids_for_ancestors_upto enabled' do
+ include_examples '#ancestors_upto'
+ end
+
+ context 'with use_traversal_ids_for_ancestors_upto disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids_for_ancestors_upto: false)
+ end
+
+ include_examples '#ancestors_upto'
+ end
+ end
+
describe '#descendants' do
let!(:another_group) { create(:group) }
let!(:another_group_nested) { create(:group, parent: another_group) }