Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2025-07-18 09:12:21 +00:00
parent f50eb2487d
commit 4e4975548a
41 changed files with 437 additions and 488 deletions

View File

@ -3185,7 +3185,6 @@ Gitlab/BoundedContexts:
- 'ee/app/services/epic_issues/list_service.rb'
- 'ee/app/services/epic_issues/update_service.rb'
- 'ee/app/services/epics/base_service.rb'
- 'ee/app/services/epics/close_service.rb'
- 'ee/app/services/epics/descendant_count_service.rb'
- 'ee/app/services/epics/epic_links/destroy_service.rb'
- 'ee/app/services/epics/epic_links/list_service.rb'

View File

@ -279,7 +279,6 @@ Layout/EmptyLineAfterMagicComment:
- 'ee/spec/services/ee/design_management/save_designs_service_spec.rb'
- 'ee/spec/services/ee/notes/quick_actions_service_spec.rb'
- 'ee/spec/services/ee/users/update_service_spec.rb'
- 'ee/spec/services/epics/close_service_spec.rb'
- 'ee/spec/services/epics/issue_promote_service_spec.rb'
- 'ee/spec/services/issue_feature_flags/list_service_spec.rb'
- 'ee/spec/services/milestones/update_service_spec.rb'

View File

@ -543,7 +543,6 @@ RSpec/BeforeAllRoleAssignment:
- 'ee/spec/services/ee/two_factor/destroy_service_spec.rb'
- 'ee/spec/services/ee/work_items/import_csv_service_spec.rb'
- 'ee/spec/services/epic_issues/update_service_spec.rb'
- 'ee/spec/services/epics/close_service_spec.rb'
- 'ee/spec/services/epics/issue_promote_service_spec.rb'
- 'ee/spec/services/epics/related_epic_links/list_service_spec.rb'
- 'ee/spec/services/epics/transfer_service_spec.rb'

View File

@ -11,7 +11,6 @@ RSpec/ExpectInLet:
- 'ee/spec/services/ee/merge_requests/reopen_service_spec.rb'
- 'ee/spec/services/ee/notes/create_service_spec.rb'
- 'ee/spec/services/ee/users/migrate_records_to_ghost_user_service_spec.rb'
- 'ee/spec/services/epics/close_service_spec.rb'
- 'ee/spec/services/groups/destroy_service_spec.rb'
- 'ee/spec/services/projects/destroy_service_spec.rb'
- 'ee/spec/services/projects/group_links/destroy_service_spec.rb'

View File

@ -869,7 +869,6 @@ RSpec/NamedSubject:
- 'ee/spec/services/epic_issues/destroy_service_spec.rb'
- 'ee/spec/services/epic_issues/list_service_spec.rb'
- 'ee/spec/services/epic_issues/update_service_spec.rb'
- 'ee/spec/services/epics/close_service_spec.rb'
- 'ee/spec/services/epics/epic_links/list_service_spec.rb'
- 'ee/spec/services/epics/issue_promote_service_spec.rb'
- 'ee/spec/services/epics/related_epic_links/destroy_service_spec.rb'

View File

@ -1 +1 @@
ab88e3894dc8f759c768d0324b2614c7274dd56d
2b6810dc5688ebdbf4024b4885fdc59686a3f1fa

View File

@ -1,9 +1,10 @@
<script>
import { GlButton, GlFormCheckbox, GlLink, GlAlert } from '@gitlab/ui';
import CiLintResults from '~/ci/pipeline_editor/components/lint/ci_lint_results.vue';
import lintCiMutation from '~/ci/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import ciLintMutation from '~/ci/pipeline_editor/graphql/mutations/ci_lint.mutation.graphql';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
import { CI_CONFIG_STATUS_VALID } from '~/ci/pipeline_editor/constants';
export default {
components: {
@ -16,10 +17,6 @@ export default {
HelpIcon,
},
props: {
endpoint: {
type: String,
required: true,
},
lintHelpPagePath: {
type: String,
required: true,
@ -28,6 +25,10 @@ export default {
type: String,
required: true,
},
projectFullPath: {
type: String,
required: true,
},
},
data() {
return {
@ -52,19 +53,25 @@ export default {
async lint() {
this.loading = true;
try {
const {
data: {
lintCI: { valid, errors, warnings, jobs },
const { data } = await this.$apollo.mutate({
mutation: ciLintMutation,
variables: {
projectPath: this.projectFullPath,
content: this.content,
dryRun: this.dryRun,
},
} = await this.$apollo.mutate({
mutation: lintCiMutation,
variables: { endpoint: this.endpoint, content: this.content, dry: this.dryRun },
});
const ciConfigData = data?.ciLint?.config || {};
const { errors, stages, warnings, status } = ciConfigData;
this.showingResults = true;
this.isValid = valid;
this.isValid = status === CI_CONFIG_STATUS_VALID;
this.errors = errors;
this.warnings = warnings;
const jobs = stages.flatMap((stage) =>
(stage.groups || []).flatMap((group) => group.jobs || []),
);
this.jobs = jobs;
} catch (error) {
this.apiError = error;

View File

@ -13,7 +13,7 @@ const apolloProvider = new VueApollo({
export default (containerId = '#js-ci-lint') => {
const containerEl = document.querySelector(containerId);
const { endpoint, lintHelpPagePath, pipelineSimulationHelpPagePath } = containerEl.dataset;
const { lintHelpPagePath, pipelineSimulationHelpPagePath, projectFullPath } = containerEl.dataset;
return new Vue({
el: containerEl,
@ -21,9 +21,9 @@ export default (containerId = '#js-ci-lint') => {
render(createElement) {
return createElement(CiLint, {
props: {
endpoint,
lintHelpPagePath,
pipelineSimulationHelpPagePath,
projectFullPath,
},
});
},

View File

@ -14,12 +14,12 @@ import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
import { helpPagePath } from '~/helpers/help_page_helper';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
import { pipelineEditorTrackingOptions } from '../../constants';
import { pipelineEditorTrackingOptions, CI_CONFIG_STATUS_VALID } from '../../constants';
import ValidatePipelinePopover from '../popovers/validate_pipeline_popover.vue';
import CiLintResults from '../lint/ci_lint_results.vue';
import getBlobContent from '../../graphql/queries/blob_content.query.graphql';
import getCurrentBranch from '../../graphql/queries/client/current_branch.query.graphql';
import lintCiMutation from '../../graphql/mutations/client/lint_ci.mutation.graphql';
import ciLintMutation from '../../graphql/mutations/ci_lint.mutation.graphql';
export const i18n = {
alertDesc: s__(
@ -159,25 +159,29 @@ export default {
this.state = VALIDATE_TAB_LOADING;
try {
const {
data: {
lintCI: { errors, jobs, valid, warnings },
},
} = await this.$apollo.mutate({
mutation: lintCiMutation,
const { data } = await this.$apollo.mutate({
mutation: ciLintMutation,
variables: {
dry: true,
projectPath: this.projectFullPath,
content: this.yaml,
endpoint: this.ciLintPath,
ref: this.currentBranch,
dryRun: true,
},
});
const ciConfigData = data?.ciLint?.config || {};
// only save the result if the user did not cancel the simulation
if (this.state === VALIDATE_TAB_LOADING) {
const { errors, stages, warnings, status } = ciConfigData;
this.errors = errors;
const jobs = stages.flatMap((stage) =>
(stage.groups || []).flatMap((group) => group.jobs || []),
);
this.jobs = jobs;
this.warnings = warnings;
this.isValid = valid;
this.isValid = status === CI_CONFIG_STATUS_VALID;
this.state = VALIDATE_TAB_RESULTS;
this.hasCiContentChanged = false;
}

View File

@ -1,21 +0,0 @@
mutation lintCI($endpoint: String, $content: String, $dry: Boolean) {
lintCI(endpoint: $endpoint, content: $content, dry_run: $dry) @client {
valid
errors
warnings
jobs {
afterScript
allowFailure
beforeScript
environment
except
name
only {
refs
}
stage
tags
when
}
}
}

View File

@ -1,4 +1,3 @@
import axios from '~/lib/utils/axios_utils';
import getAppStatus from './queries/client/app_status.query.graphql';
import getCurrentBranch from './queries/client/current_branch.query.graphql';
import getLastCommitBranch from './queries/client/last_commit_branch.query.graphql';
@ -6,38 +5,6 @@ import getPipelineEtag from './queries/client/pipeline_etag.query.graphql';
export const resolvers = {
Mutation: {
lintCI: (_, { endpoint, content, dry_run }) => {
return axios.post(endpoint, { content, dry_run }).then(({ data }) => {
const { errors, warnings, valid, jobs } = data;
return {
valid,
errors,
warnings,
jobs: jobs.map((job) => {
const only = job.only
? { refs: job.only.refs, __typename: 'CiLintJobOnlyPolicy' }
: null;
return {
name: job.name,
stage: job.stage,
beforeScript: job.before_script,
script: job.script,
afterScript: job.after_script,
tags: job.tag_list,
environment: job.environment,
when: job.when,
allowFailure: job.allow_failure,
only,
except: job.except,
__typename: 'CiLintJob',
};
}),
__typename: 'CiLintContent',
};
});
},
updateAppStatus: (_, { appStatus }, { cache }) => {
cache.writeQuery({
query: getAppStatus,

View File

@ -2,6 +2,6 @@ import { formatGraphQLGroups } from '~/vue_shared/components/groups_list/formatt
export const formatGroups = (groups) =>
formatGraphQLGroups(groups, (group) => ({
editPath: `${group.relativeWebUrl}/-/edit`,
editPath: `${group.webUrl}/-/edit`,
avatarLabel: group.name,
}));

View File

@ -136,7 +136,7 @@ export default {
return `@${this.user?.username}`;
},
cssClasses() {
const classList = ['user-popover', 'gl-max-w-48', 'gl-overflow-hidden'];
const classList = ['user-popover', 'gl-w-34', 'gl-overflow-hidden'];
if (this.userCannotMerge) {
classList.push('user-popover-cannot-merge');

View File

@ -189,21 +189,6 @@ $avatar-sizes: (
}
.user-popover {
// GlAvatarLabeled doesn't expose any prop to override internal classes
// Max width of popover container is set by gl-max-w-48
// so we need to ensure that name/username/status container doesn't overflow
// stylelint-disable-next-line gitlab/no-gl-class
.gl-avatar-labeled-labels {
max-width: px-to-rem(290px);
}
// stylelint-disable-next-line gitlab/no-gl-class
.gl-avatar-labeled-label,
.gl-avatar-labeled-sublabel {
@apply gl-truncate;
}
&.user-popover-cannot-merge {
.popover-header {
background-color: var(--gl-feedback-warning-background-color);

View File

@ -9,8 +9,6 @@ class ProjectStatistics < ApplicationRecord
attribute :wiki_size, default: 0
attribute :snippets_size, default: 0
ignore_column :vulnerability_count, remove_with: '17.7', remove_after: '2024-11-15'
counter_attribute :build_artifacts_size
counter_attribute :packages_size

View File

@ -3,4 +3,4 @@
%h4.pt-3.pb-3= _("Validate your GitLab CI configuration")
#js-ci-lint{ data: { endpoint: project_ci_lint_path(@project), pipeline_simulation_help_page_path: help_page_path('ci/yaml/lint.md', anchor: 'simulate-a-pipeline') , lint_help_page_path: help_page_path('ci/yaml/lint.md', anchor: 'check-cicd-syntax') } }
#js-ci-lint{ data: { pipeline_simulation_help_page_path: help_page_path('ci/yaml/lint.md', anchor: 'simulate-a-pipeline') , lint_help_page_path: help_page_path('ci/yaml/lint.md', anchor: 'check-cicd-syntax'), project_full_path: @project.full_path } }

View File

@ -1,8 +1,9 @@
---
migration_job_name: DeleteOrphanedRoutes
description: Deletes the orphaned routes that were not deleted by the loose foreign key
description: Deletes the orphaned routes that were not deleted by the loose foreign
key
feature_category: groups_and_projects
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186659
milestone: '17.11'
queued_migration_version: 20250401113424
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250717232515'

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkDeleteOrphanedRoutes < Gitlab::Database::Migration[2.3]
milestone '18.3'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'DeleteOrphanedRoutes',
table_name: :routes,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
fe8ddd9c103327d9e10e8c55deb3d581470ab8f5ff6b86958bfb3a613f21520f

View File

@ -2121,6 +2121,7 @@ Input type: `AdminSidekiqQueuesDeleteJobsInput`
| <a id="mutationadminsidekiqqueuesdeletejobsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationadminsidekiqqueuesdeletejobsfeaturecategory"></a>`featureCategory` | [`String`](#string) | Delete jobs matching feature_category in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsjobid"></a>`jobId` | [`String`](#string) | Delete jobs matching job_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobskubernetesagentid"></a>`kubernetesAgentId` | [`String`](#string) | Delete jobs matching kubernetes_agent_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsmergeactionstatus"></a>`mergeActionStatus` | [`String`](#string) | Delete jobs matching merge_action_status in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobsorganizationid"></a>`organizationId` | [`String`](#string) | Delete jobs matching organization_id in the context metadata. |
| <a id="mutationadminsidekiqqueuesdeletejobspipelineid"></a>`pipelineId` | [`String`](#string) | Delete jobs matching pipeline_id in the context metadata. |

View File

@ -372,6 +372,69 @@ You can show scanner findings in the diff. For details, see:
- [Code Quality findings](../../../ci/testing/code_quality.md#merge-request-changes-view)
- [Static Analysis findings](../../application_security/sast/_index.md#merge-request-changes-view)
## Download merge request changes
You can download the changes included in a merge request for use outside of GitLab.
### As a diff
To download the changes as a diff:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests** and find your merge request.
1. Select the merge request.
1. In the upper-right corner, select **Code > Plain diff**.
If you know the URL of the merge request, you can also download the diff from
the command line by appending `.diff` to the URL. This example downloads the diff
for merge request `000000`:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.diff
```
To download and apply the diff in a one-line CLI command:
```shell
curl "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.diff" | git apply
```
### As a patch file
To download the changes as a patch file:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests** and find your merge request.
1. Select the merge request.
1. In the upper-right corner, select **Code > Patches**.
If you know the URL of the merge request, you can also download the patch from
the command line by appending `.patch` to the URL. This example downloads the patch
file for merge request `000000`:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.patch
```
To download and apply the patch using [`git am`](https://git-scm.com/docs/git-am):
```shell
# Download and preview the patch
curl "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.patch" > changes.patch
git apply --check changes.patch
# Apply the patch
git am changes.patch
```
You can also download and apply the patch in a single command:
```shell
curl "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.patch" | git am
```
The `git am` uses the `-p1` option by default. For more information, see [`git-apply`](https://git-scm.com/docs/git-apply).
## Add a comment to a merge request file
{{< history >}}
@ -400,3 +463,10 @@ This comment can also be a thread.
1. Select the location where you want to comment.
GitLab shows an icon and a comment field on the image.
## Related topics
- [Compare branches](../repository/branches/_index.md#compare-branches)
- [Download branch comparisons](../repository/branches/_index.md#download-branch-comparisons)
- [Merge request reviews](reviews/_index.md)
- [Merge request versions](versions.md)

View File

@ -295,51 +295,9 @@ another user with permission to merge the merge request can override this check:
## Download merge request changes
### As a diff
To download the changes included in a merge request as a diff:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests**.
1. Select your merge request.
1. In the upper-right corner, select **Code > Plain diff**.
If you know the URL of the merge request, you can also download the diff from
the command line by appending `.diff` to the URL. This example downloads the diff
for merge request `000000`:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.diff
```
To download and apply the diff in a one-line CLI command:
```shell
curl "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.diff" | git apply
```
### As a patch file
To download the changes included in a merge request as a patch file:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests**.
1. Select your merge request.
1. In the upper-right corner, select **Code > Patches**.
If you know the URL of the merge request, you can also download the patch from
the command line by appending `.patch` to the URL. This example downloads the patch
file for merge request `000000`:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.patch
```
To download and apply the patch in a one-line CLI command using [`git am`](https://git-scm.com/docs/git-am):
```shell
curl "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/000000.patch" | git am
```
You can download the changes from a merge request as a diff or patch file.
For more information and examples, see
[Download merge request changes](../changes.md#download-merge-request-changes).
## Associated features
@ -347,6 +305,8 @@ Merge requests are related to these features:
- [Cherry-pick changes](../cherry_pick_changes.md):
In the GitLab UI, select **Cherry-pick** in a merged merge request or a commit to cherry-pick it.
- [Compare changes](../changes.md):
View and download the diff of changes included in a merge request.
- [Fast-forward merge requests](../methods/_index.md#fast-forward-merge):
For a linear Git history and a way to accept merge requests without creating merge commits
- [Find the merge request that introduced a change](../versions.md):
@ -363,5 +323,7 @@ Merge requests are related to these features:
## Related topics
- [Compare changes in merge requests](../changes.md)
- [Compare branches](../../repository/branches/_index.md#compare-branches)
- [Merge methods](../methods/_index.md)
- [Draft Notes API](../../../../api/draft_notes.md)

View File

@ -275,6 +275,71 @@ To compare branches in a repository:
1. Select **Compare** to show the list of commits, and changed files.
1. Optional. To reverse the **Source** and **Target**, select **Swap revisions** ({{< icon name="substitute" >}}).
### Download branch comparisons
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217206) in GitLab 18.3.
{{< /history >}}
You can download the comparison between branches as a diff or patch file for use outside of GitLab.
#### As a diff
To download the branch comparison as a diff, add `format=diff` to the compare URL:
- If the URL has no query parameters, append `?format=diff`:
```plaintext
https://gitlab.example.com/my-group/my-project/-/compare/main...feature-branch?format=diff
```
- If the URL already has query parameters, append `&format=diff`:
```plaintext
https://gitlab.example.com/my-group/my-project/-/compare/main...feature-branch?from_project_id=2&format=diff
```
To download and apply the diff:
```shell
curl "https://gitlab.example.com/my-group/my-project/-/compare/main...feature-branch?format=diff" | git apply
```
#### As a patch file
To download the branch comparison as a patch file, add `format=patch` to the compare URL:
- If the URL has no query parameters, append `?format=patch`:
```plaintext
https://gitlab.example.com/my-group/my-project/-/compare/main...feature-branch?format=patch
```
- If the URL already has query parameters, append `&format=patch`:
```plaintext
https://gitlab.example.com/my-group/my-project/-/compare/main...feature-branch?from_project_id=2&format=patch
```
To download and apply the patch using [`git am`](https://git-scm.com/docs/git-am):
```shell
# Download and preview the patch
curl "https://gitlab.example.com/my-group/my-project/-/compare/main...feature-branch?format=patch" > changes.patch
git apply --check changes.patch
# Apply the patch
git am changes.patch
```
You can also download and apply the patch in a single command:
```shell
curl "https://gitlab.example.com/my-group/my-project/-/compare/main...feature-branch?format=patch" | git am
```
## Delete merged branches
Merged branches can be deleted in bulk if they meet all of these criteria:
@ -381,6 +446,9 @@ To do this:
## Related topics
- [Protected branches](protected.md)
- [Branch rules](branch_rules.md)
- [Compare changes in merge requests](../../merge_requests/changes.md)
- [Download merge request changes](../../merge_requests/changes.md#download-merge-request-changes)
- [Branches API](../../../../api/branches.md)
- [Protected Branches API](../../../../api/protected_branches.md)
- [Getting started with Git](../../../../topics/git/_index.md)

View File

@ -86,7 +86,7 @@ Prerequisites:
To allow a cluster agent for workspaces in a group:
1. On the left sidebar, select **Search or go to** and find your group.
1. On the left sidebar, select **Settings > Workspaces**.
1. On the left sidebar, select **Settings** > **Workspaces**.
1. In the **Group agents** section, select the **All agents** tab.
1. From the list of available agents, find the agent with status **Blocked**, and select **Allow**.
1. On the confirmation dialog, select **Allow agent**.
@ -103,7 +103,7 @@ Prerequisites:
To remove an allowed cluster agent from a group:
1. On the left sidebar, select **Search or go to** and find your group.
1. On the left sidebar, select **Settings > Workspaces**.
1. On the left sidebar, select **Settings** > **Workspaces**.
1. In the **Group agents** section, select the **Allowed agents** tab.
1. From the list of allowed agents, find the agent you want to remove, and select **Block**.
1. On the confirmation dialog, select **Block agent**.

View File

@ -135,7 +135,7 @@ Only one agent is required. You can create workspaces from all projects in a gro
To allow your GitLab agent for Kubernetes in a group and make it available to all projects in that group:
1. On the left sidebar, select **Search or go to** and find your group.
1. Select **Settings > Workspaces**.
1. Select **Settings** > **Workspaces**.
1. In the **Group agents** section, select the **All agents** tab.
1. For the GitLab agent for Kubernetes, select **Allow**.
1. On the confirmation dialog, select **Allow agent**.

View File

@ -156,7 +156,7 @@ To create a token for the agent:
1. Go to your group.
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Operate > Kubernetes clusters**.
1. Select **Operate** > **Kubernetes clusters**.
1. Select **Connect a cluster**.
1. Enter a name for your agent and save for later use. For example, `gitlab-workspaces-agentk-eks`.
1. Select **Create and register**.
@ -194,7 +194,7 @@ pipeline can run.
To configure CI/CD variables:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > CI/CD**.
1. Select **Settings** > **CI/CD**.
1. Expand **Variables**.
1. In the **Project variables** section, add the following required variables:
@ -271,7 +271,7 @@ resources in AWS.
To run the pipeline:
1. Create a new pipeline in your GitLab project:
1. On the left sidebar, select **Build > Pipelines**.
1. On the left sidebar, select **Build** > **Pipelines**.
1. Select **New pipeline** and select **New pipeline** again to confirm.
1. Verify the `plan` job succeeds, then manually trigger the `apply` job.
@ -309,7 +309,7 @@ Next, you'll authorize the GitLab agent for Kubernetes to connect to your GitLab
To authorize the agent:
1. On the left sidebar, select **Search or go to** and find your group.
1. Select **Settings > Workspaces**.
1. Select **Settings** > **Workspaces**.
1. In the **Group agents** section, select the **All agents** tab.
1. From the list of available agents, find the agent with status **Blocked**, and select **Allow**.
1. On the confirmation dialog, select **Allow agent**.

View File

@ -144,6 +144,10 @@ module API
access_token.user || raise(UnauthorizedError)
end
def set_agent_on_context(agent:)
::Gitlab::ApplicationContext.push(kubernetes_agent: agent)
end
def access_token
return unless params[:access_key].present?

View File

@ -36,7 +36,8 @@ module Gitlab
:auth_fail_token_id,
:auth_fail_requested_scopes,
:http_router_rule_action,
:http_router_rule_type
:http_router_rule_type,
:kubernetes_agent_id
].freeze
private_constant :KNOWN_KEYS
@ -73,7 +74,8 @@ module Gitlab
Attribute.new(:auth_fail_token_id, String),
Attribute.new(:auth_fail_requested_scopes, String),
Attribute.new(:http_router_rule_action, String),
Attribute.new(:http_router_rule_type, String)
Attribute.new(:http_router_rule_type, String),
Attribute.new(:kubernetes_agent, ::Clusters::Agent)
].freeze
private_constant :APPLICATION_ATTRIBUTES
@ -163,6 +165,7 @@ module Gitlab
hash[:pipeline_id] = -> { job&.pipeline_id } if set_values.include?(:job)
hash[:job_id] = -> { job&.id } if set_values.include?(:job)
hash[:artifact_size] = -> { artifact&.size } if set_values.include?(:artifact)
hash[:kubernetes_agent_id] = -> { kubernetes_agent&.id } if set_values.include?(:kubernetes_agent)
end
end
# rubocop: enable Metrics/CyclomaticComplexity

View File

@ -22,7 +22,7 @@ module QA
#
# @return [String]
def sandbox_name
return "gitlab-e2e-sandbox-group-#{Time.now.wday + 1}" if live_env?
return "gitlab-e2e-sandbox-group-#{sandbox_number}" if live_env?
"e2e-sandbox-#{SecureRandom.hex(6)}"
end
@ -39,6 +39,23 @@ module QA
# There is no case to change gitlab address in the middle of test process so it should be safe to do
@live_env = Runtime::Env.running_on_live_env?
end
# Determines the sandbox group number for live environments
#
# Live environments (like .com) have exactly 8 pre-created sandbox groups
# (gitlab-e2e-sandbox-group-1 through gitlab-e2e-sandbox-group-8). This method
# maps CI_NODE_INDEX values to the available 1-8 range using modulo arithmetic
# to handle parallel jobs with more than 8 nodes. When CI_NODE_INDEX is not
# set, returns a memoized random number between 1-8.
#
# @return [Integer] A number between 1 and 8 inclusive, corresponding to available sandbox groups
# @example
# ENV['CI_NODE_INDEX'] = '1' # returns 1 (gitlab-e2e-sandbox-group-1)
# ENV['CI_NODE_INDEX'] = '9' # returns 1 (wraps to gitlab-e2e-sandbox-group-1)
# ENV['CI_NODE_INDEX'] = nil # returns random 1-8 (memoized)
def sandbox_number
ENV['CI_NODE_INDEX'] ? ((ENV['CI_NODE_INDEX'].to_i - 1) % 8) + 1 : @random_sandbox_id ||= rand(1..8)
end
end
end
end

View File

@ -9,6 +9,22 @@ RSpec.describe QA::Runtime::Namespace do
described_class.instance_variable_set(:@time, nil)
end
shared_examples "sandbox naming for live environments" do
context "when the job does not use parallel" do
it "returns a random sandbox name 1-8" do
expect(described_class.sandbox_name).to match(%r{gitlab-e2e-sandbox-group-[1-8]})
end
end
context "when the job uses parallel" do
it "returns sandbox name based on CI_NODE_INDEX" do
stub_env('CI_NODE_INDEX', '3')
expect(described_class.sandbox_name).to match('gitlab-e2e-sandbox-group-3')
end
end
end
describe '.group_name' do
it "returns unique name with predefined pattern" do
expect(described_class.group_name).to match(/e2e-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}-[a-f0-9]{16}/)
@ -30,17 +46,13 @@ RSpec.describe QA::Runtime::Namespace do
context "when running on .com environment" do
let(:dot_com) { true }
it "returns day specific sandbox name" do
expect(described_class.sandbox_name).to match(%r{gitlab-e2e-sandbox-group-#{time.wday + 1}})
end
it_behaves_like "sandbox naming for live environments"
end
context "when running on release environment" do
let(:release) { true }
it "returns day specific sandbox name" do
expect(described_class.sandbox_name).to match(%r{gitlab-e2e-sandbox-group-#{time.wday + 1}})
end
it_behaves_like "sandbox naming for live environments"
end
context "when running on ephemeral environment" do

View File

@ -44,11 +44,22 @@ RSpec.describe 'CI Lint', :js, feature_category: :pipeline_composition do
end
context 'YAML is incorrect' do
let(:yaml_content) { 'value: cannot have :' }
let(:yaml_content) do
<<~YAML.strip
invalid: yaml content
that: has
multiple: lines
value: cannot have :
more: content
YAML
end
it 'displays information about an error' do
expect(page).to have_content('Status: Syntax is incorrect')
expect(page).to have_selector(content_selector, text: yaml_content)
expect(page).to have_selector(content_selector)
rendered_content = find(content_selector).text
expect(rendered_content.strip).to eq(yaml_content.strip)
end
end
end

View File

@ -1,22 +1,28 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import CiLint from '~/ci/ci_lint/components/ci_lint.vue';
import CiLintResults from '~/ci/pipeline_editor/components/lint/ci_lint_results.vue';
import lintCIMutation from '~/ci/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import ciLintMutation from '~/ci/pipeline_editor/graphql/mutations/ci_lint.mutation.graphql';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
import { mockLintDataValid } from '../mock_data';
import { mockCiLintMutationResponse } from '../../pipeline_editor/mock_data';
Vue.use(VueApollo);
describe('CI Lint', () => {
let wrapper;
let mockCiLintData;
const endpoint = '/namespace/project/-/ci/lint';
const content =
"test_job:\n stage: build\n script: echo 'Building'\n only:\n - web\n - chat\n - pushes\n allow_failure: true ";
const mockMutate = jest.fn().mockResolvedValue(mockLintDataValid);
const createComponent = () => {
const handlers = [[ciLintMutation, mockCiLintData]];
const mockApollo = createMockApollo(handlers);
wrapper = shallowMount(CiLint, {
data() {
return {
@ -24,15 +30,11 @@ describe('CI Lint', () => {
};
},
propsData: {
endpoint,
pipelineSimulationHelpPagePath: '/help/ci/lint#pipeline-simulation',
lintHelpPagePath: '/help/ci/lint#anchor',
projectFullPath: '/path/to/project',
},
mocks: {
$apollo: {
mutate: mockMutate,
},
},
apolloProvider: mockApollo,
});
};
@ -44,37 +46,40 @@ describe('CI Lint', () => {
const findDryRunToggle = () => wrapper.find('[data-testid="ci-lint-dryrun"]');
beforeEach(() => {
createComponent();
});
afterEach(() => {
mockMutate.mockClear();
mockCiLintData = jest.fn();
});
it('displays the editor', () => {
createComponent();
expect(findEditor().exists()).toBe(true);
});
it('validate action calls mutation correctly', () => {
createComponent();
findValidateBtn().vm.$emit('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: lintCIMutation,
variables: { content, dry: false, endpoint },
expect(mockCiLintData).toHaveBeenCalledWith({
projectPath: '/path/to/project',
content,
dryRun: false,
});
});
it('validate action calls mutation with dry run', () => {
createComponent();
findDryRunToggle().vm.$emit('input', true);
findValidateBtn().vm.$emit('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: lintCIMutation,
variables: { content, dry: true, endpoint },
expect(mockCiLintData).toHaveBeenCalledWith({
projectPath: '/path/to/project',
content,
dryRun: true,
});
});
it('validation displays results', async () => {
mockCiLintData.mockResolvedValue(mockCiLintMutationResponse);
createComponent();
findValidateBtn().vm.$emit('click');
await nextTick();
@ -88,7 +93,8 @@ describe('CI Lint', () => {
});
it('validation displays error', async () => {
mockMutate.mockRejectedValue('Error!');
mockCiLintData.mockRejectedValueOnce(new Error('Error!'));
createComponent();
findValidateBtn().vm.$emit('click');
@ -99,11 +105,12 @@ describe('CI Lint', () => {
await waitForPromises();
expect(findCiLintResults().exists()).toBe(false);
expect(findAlert().text()).toBe('Error!');
expect(findAlert().text()).toBe('Error: Error!');
expect(findValidateBtn().props('loading')).toBe(false);
});
it('content is cleared on clear action', async () => {
createComponent();
expect(findEditor().props('value')).toBe(content);
await findClearBtn().vm.$emit('click');

View File

@ -1,42 +1,47 @@
import { mockJobs } from 'jest/ci/pipeline_editor/mock_data';
import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
export const mockLintDataError = {
data: {
lintCI: {
errors: ['Error message'],
warnings: ['Warning message'],
valid: false,
jobs: mockJobs.map((j) => {
const job = { ...j, tags: j.tagList };
delete job.tagList;
return job;
}),
},
export const mockCiLintJobs = [
{
beforeScript: [],
afterScript: [],
environment: null,
allowFailure: false,
tags: [],
when: 'on_success',
only: { refs: ['branches', 'tags'], __typename: 'CiJobLimitType' },
except: null,
needs: [{ name: 'test', __typename: 'CiConfigNeed' }],
__typename: 'CiConfigJobV2',
name: 'job_test_1',
script: ['echo "test 1"'],
stage: 'test',
},
};
export const mockLintDataValid = {
data: {
lintCI: {
errors: [],
warnings: [],
valid: true,
jobs: mockJobs.map((j) => {
const job = { ...j, tags: j.tagList };
delete job.tagList;
return job;
}),
},
{
name: 'job_test_2',
script: ['echo "test 2"'],
stage: 'test',
beforeScript: [],
afterScript: [],
environment: null,
allowFailure: false,
tags: [],
when: 'on_success',
only: { refs: ['branches', 'tags'], __typename: 'CiJobLimitType' },
except: null,
needs: [{ name: 'test', __typename: 'CiConfigNeed' }],
__typename: 'CiConfigJobV2',
},
};
export const mockLintDataErrorRest = {
...mockLintDataError.data.lintCI,
jobs: mockJobs.map((j) => convertObjectPropsToSnakeCase(j)),
};
export const mockLintDataValidRest = {
...mockLintDataValid.data.lintCI,
jobs: mockJobs.map((j) => convertObjectPropsToSnakeCase(j)),
};
{
name: 'job_build',
script: ['echo "build"'],
stage: 'build',
beforeScript: [],
afterScript: [],
environment: null,
allowFailure: false,
tags: [],
when: 'on_success',
only: { refs: ['branches', 'tags'], __typename: 'CiJobLimitType' },
except: null,
needs: [{ name: 'test', __typename: 'CiConfigNeed' }],
__typename: 'CiConfigJobV2',
},
];

View File

@ -1,43 +1,38 @@
import Vue from 'vue';
import { GlAlert, GlDisclosureDropdown, GlEmptyState, GlLoadingIcon, GlPopover } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
import CiLintResults from '~/ci/pipeline_editor/components/lint/ci_lint_results.vue';
import CiValidate, { i18n } from '~/ci/pipeline_editor/components/validate/ci_validate.vue';
import ValidatePipelinePopover from '~/ci/pipeline_editor/components/popovers/validate_pipeline_popover.vue';
import getBlobContent from '~/ci/pipeline_editor/graphql/queries/blob_content.query.graphql';
import ciLintMutation from '~/ci/pipeline_editor/graphql/mutations/ci_lint.mutation.graphql';
import getCurrentBranch from '~/ci/pipeline_editor/graphql/queries/client/current_branch.query.graphql';
import { pipelineEditorTrackingOptions } from '~/ci/pipeline_editor/constants';
import {
mockBlobContentQueryResponse,
mockCiLintMutationResponse,
ciLintErrorResponse,
mockCiLintPath,
mockCiYml,
mockCurrentBranchResponse,
mockDefaultBranch,
mockSimulatePipelineHelpPagePath,
} from '../../mock_data';
import {
mockLintDataError,
mockLintDataValid,
mockLintDataErrorRest,
mockLintDataValidRest,
} from '../../../ci_lint/mock_data';
let mockAxios;
import { mockCiLintJobs } from '../../../ci_lint/mock_data';
Vue.use(VueApollo);
const defaultProvide = {
ciConfigPath: '/path/to/ci-config',
ciLintPath: mockCiLintPath,
currentBranch: 'main',
projectFullPath: '/path/to/project',
validateTabIllustrationPath: '/path/to/img',
simulatePipelineHelpPagePath: mockSimulatePipelineHelpPagePath,
@ -46,12 +41,21 @@ const defaultProvide = {
describe('Pipeline Editor Validate Tab', () => {
let wrapper;
let mockBlobContentData;
let mockCiLintData;
let trackingSpy;
const createComponent = ({ props, stubs } = {}) => {
const handlers = [[getBlobContent, mockBlobContentData]];
const handlers = [
[getBlobContent, mockBlobContentData],
[ciLintMutation, mockCiLintData],
];
const mockApollo = createMockApollo(handlers, resolvers);
mockApollo.clients.defaultClient.cache.writeQuery({
query: getCurrentBranch,
data: mockCurrentBranchResponse,
});
wrapper = shallowMountExtended(CiValidate, {
propsData: {
ciFileContent: mockCiYml,
@ -80,14 +84,8 @@ describe('Pipeline Editor Validate Tab', () => {
const findResultsCta = () => wrapper.findByTestId('resimulate-pipeline-button');
beforeEach(() => {
mockAxios = new MockAdapter(axios);
mockAxios.onPost(defaultProvide.ciLintPath).reply(HTTP_STATUS_OK, mockLintDataValidRest);
mockBlobContentData = jest.fn();
});
afterEach(() => {
mockAxios.restore();
mockCiLintData = jest.fn();
});
describe('while initial CI content is loading', () => {
@ -115,7 +113,9 @@ describe('Pipeline Editor Validate Tab', () => {
expect(findPipelineSource().props('disabled')).toBe(true);
});
it('renders enabled CTA without tooltip', () => {
it('renders enabled CTA without tooltip', async () => {
await waitForPromises();
expect(findCta().exists()).toBe(true);
expect(findCta().props('disabled')).toBe(false);
expect(findDisabledCtaTooltip().exists()).toBe(false);
@ -162,24 +162,25 @@ describe('Pipeline Editor Validate Tab', () => {
expect(findCta().props('loading')).toBe(true);
});
it('calls endpoint with the correct input', async () => {
it('calls ciLint mutation with the correct input', async () => {
findCta().vm.$emit('click');
await waitForPromises();
expect(mockAxios.history.post).toHaveLength(1);
expect(mockAxios.history.post[0].data).toBe(
JSON.stringify({
content: mockCiYml,
dry_run: true,
}),
);
expect(mockCiLintData).toHaveBeenCalledWith({
projectPath: defaultProvide.projectFullPath,
content: mockCiYml,
ref: mockDefaultBranch,
dryRun: true,
});
});
describe('when results are successful', () => {
beforeEach(async () => {
findCta().vm.$emit('click');
mockCiLintData.mockResolvedValue(mockCiLintMutationResponse);
await createComponent();
findCta().vm.$emit('click');
await waitForPromises();
});
@ -200,14 +201,15 @@ describe('Pipeline Editor Validate Tab', () => {
dryRun: true,
hideAlert: true,
isValid: true,
jobs: mockLintDataValid.data.lintCI.jobs,
jobs: mockCiLintJobs,
});
});
});
describe('when results have errors', () => {
beforeEach(async () => {
mockAxios.onPost(defaultProvide.ciLintPath).reply(HTTP_STATUS_OK, mockLintDataErrorRest);
mockCiLintData.mockResolvedValue(ciLintErrorResponse);
findCta().vm.$emit('click');
await waitForPromises();
@ -225,8 +227,8 @@ describe('Pipeline Editor Validate Tab', () => {
dryRun: true,
hideAlert: true,
isValid: false,
errors: mockLintDataError.data.lintCI.errors,
warnings: mockLintDataError.data.lintCI.warnings,
errors: ciLintErrorResponse.data.ciLint.config.errors,
warnings: ciLintErrorResponse.data.ciLint.config.warnings,
});
});
});
@ -234,6 +236,7 @@ describe('Pipeline Editor Validate Tab', () => {
describe('when CI content has changed after a simulation', () => {
beforeEach(async () => {
mockCiLintData.mockResolvedValue(mockCiLintMutationResponse);
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
await createComponent();
@ -272,13 +275,13 @@ describe('Pipeline Editor Validate Tab', () => {
await waitForPromises();
expect(mockAxios.history.post).toHaveLength(2);
expect(mockAxios.history.post[1].data).toBe(
JSON.stringify({
content: newContent,
dry_run: true,
}),
);
expect(mockCiLintData).toHaveBeenCalledTimes(2);
expect(mockCiLintData).toHaveBeenCalledWith({
content: 'new yaml content',
dryRun: true,
projectPath: '/path/to/project',
ref: mockDefaultBranch,
});
});
});

View File

@ -1,73 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`~/ci/pipeline_editor/graphql/resolvers Mutation lintCI lint data is as expected 1`] = `
{
"__typename": "CiLintContent",
"errors": [],
"jobs": [
{
"__typename": "CiLintJob",
"afterScript": [
"echo 'after script 1",
],
"allowFailure": false,
"beforeScript": [
"echo 'before script 1'",
],
"environment": "prd",
"except": {
"refs": [
"main@gitlab-org/gitlab",
"/^release/.*$/@gitlab-org/gitlab",
],
},
"name": "job_1",
"only": null,
"script": [
"echo 'script 1'",
],
"stage": "test",
"tags": [
"tag 1",
],
"when": "on_success",
},
{
"__typename": "CiLintJob",
"afterScript": [
"echo 'after script 2",
],
"allowFailure": true,
"beforeScript": [
"echo 'before script 2'",
],
"environment": "stg",
"except": {
"refs": [
"main@gitlab-org/gitlab",
"/^release/.*$/@gitlab-org/gitlab",
],
},
"name": "job_2",
"only": {
"__typename": "CiLintJobOnlyPolicy",
"refs": [
"web",
"chat",
"pushes",
],
},
"script": [
"echo 'script 2'",
],
"stage": "test",
"tags": [
"tag 2",
],
"when": "on_success",
},
],
"valid": true,
"warnings": [],
}
`;

View File

@ -1,52 +0,0 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers';
import { mockLintResponse } from '../mock_data';
jest.mock('~/api', () => {
return {
getRawFile: jest.fn(),
};
});
describe('~/ci/pipeline_editor/graphql/resolvers', () => {
describe('Mutation', () => {
describe('lintCI', () => {
let mock;
let result;
const endpoint = '/ci/lint';
beforeEach(async () => {
mock = new MockAdapter(axios);
mock.onPost(endpoint).reply(HTTP_STATUS_OK, mockLintResponse);
result = await resolvers.Mutation.lintCI(null, {
endpoint,
content: 'content',
dry_run: true,
});
});
afterEach(() => {
mock.restore();
});
/* eslint-disable no-underscore-dangle */
it('lint data has correct type names', () => {
expect(result.__typename).toBe('CiLintContent');
expect(result.jobs[0].__typename).toBe('CiLintJob');
expect(result.jobs[1].__typename).toBe('CiLintJob');
expect(result.jobs[1].only.__typename).toBe('CiLintJobOnlyPolicy');
});
/* eslint-enable no-underscore-dangle */
it('lint data is as expected', () => {
expect(result).toMatchSnapshot();
});
});
});
});

View File

@ -225,90 +225,6 @@ export const mockCiLintMutationResponse = {
},
};
// Mock result of the graphql query at:
// app/assets/javascripts/ci/pipeline_editor/graphql/queries/ci_config.graphql
export const mockCiConfigQueryResponse = {
data: {
ciConfig: {
errors: [],
includes: mockIncludes,
mergedYaml: mockCiYml,
status: CI_CONFIG_STATUS_VALID,
stages: {
__typename: 'CiConfigStageConnection',
nodes: [
{
name: 'test',
groups: {
nodes: [
{
id: 'group-1',
name: 'job_test_1',
size: 1,
jobs: {
nodes: [
{
name: 'job_test_1',
script: ['echo "test 1"'],
...mockJobFields,
},
],
__typename: 'CiConfigJobConnection',
},
__typename: 'CiConfigGroup',
},
{
id: 'group-2',
name: 'job_test_2',
size: 1,
jobs: {
nodes: [
{
name: 'job_test_2',
script: ['echo "test 2"'],
...mockJobFields,
},
],
__typename: 'CiConfigJobConnection',
},
__typename: 'CiConfigGroup',
},
],
__typename: 'CiConfigGroupConnection',
},
__typename: 'CiConfigStage',
},
{
name: 'build',
groups: {
nodes: [
{
name: 'job_build',
size: 1,
jobs: {
nodes: [
{
name: 'job_build',
script: ['echo "build"'],
...mockJobFields,
},
],
__typename: 'CiConfigJobConnection',
},
__typename: 'CiConfigGroup',
},
],
__typename: 'CiConfigGroupConnection',
},
__typename: 'CiConfigStage',
},
],
},
__typename: 'CiConfig',
},
},
};
export const mockMergedConfig = (mergedConfig) => {
const { config } = mockCiLintMutationResponse.data.ciLint;
return {
@ -317,6 +233,24 @@ export const mockMergedConfig = (mergedConfig) => {
};
};
export const ciLintErrorResponse = {
data: {
ciLint: {
config: {
errors: ['header:spec:inputs:app_target_region config contains unknown keys: required'],
warnings: [],
includes: null,
mergedYaml: null,
status: 'INVALID',
stages: [],
__typename: 'CiConfigV2',
},
errors: [],
__typename: 'CiLintPayload',
},
},
};
export const mockCommitShaResults = {
data: {
project: {
@ -570,3 +504,13 @@ export const mockCommitCreateResponseNewEtag = {
},
},
};
export const mockCurrentBranchResponse = {
workBranches: {
__typename: 'BranchList',
current: {
__typename: 'WorkBranch',
name: mockDefaultBranch,
},
},
};

View File

@ -10,7 +10,7 @@ describe('formatGroups', () => {
expect(formatGroups(graphQLGroups)).toEqual(
formatGraphQLGroups(graphQLGroups).map((group) => ({
...group,
editPath: `${group.relativeWebUrl}/-/edit`,
editPath: `${group.webUrl}/-/edit`,
avatarLabel: group.name,
children: expect.any(Object),
})),

View File

@ -250,6 +250,16 @@ RSpec.describe Gitlab::ApplicationContext, feature_category: :shared do
expect(result(context)).to include(organization_id: organization.id)
end
end
context 'when using kubernetes agent context' do
let_it_be(:cluster_agent) { create(:cluster_agent) }
it 'sets the kubernetes_agent_id value' do
context = described_class.new(kubernetes_agent: cluster_agent)
expect(result(context)).to include(kubernetes_agent_id: cluster_agent.id)
end
end
end
describe '#use' do

View File

@ -2016,7 +2016,6 @@
- './ee/spec/services/epic_issues/destroy_service_spec.rb'
- './ee/spec/services/epic_issues/list_service_spec.rb'
- './ee/spec/services/epic_issues/update_service_spec.rb'
- './ee/spec/services/epics/close_service_spec.rb'
- './ee/spec/services/epics/descendant_count_service_spec.rb'
- './ee/spec/services/epics/epic_links/destroy_service_spec.rb'
- './ee/spec/services/epics/epic_links/list_service_spec.rb'

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples_for 'service scheduling async deletes' do
it 'destroys associated todos asynchronously', :sidekiq_inline do
it 'destroys associated todos asynchronously' do
expect(worker_class).to receive(:perform_async).with(issuable.id, issuable.class.base_class.name)
if try(:sync_object).present?
@ -11,7 +11,7 @@ RSpec.shared_examples_for 'service scheduling async deletes' do
subject.execute(issuable)
end
it 'works inside a transaction', :sidekiq_inline do
it 'works inside a transaction' do
expect(worker_class).to receive(:perform_async).with(issuable.id, issuable.class.base_class.name)
if try(:sync_object).present?