Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6a6e9bec88
commit
e79ee0307d
2
Gemfile
2
Gemfile
|
|
@ -245,7 +245,7 @@ gem 'gitlab-sidekiq-fetcher', path: 'vendor/gems/sidekiq-reliable-fetch', requir
|
|||
gem 'fugit', '~> 1.8.1'
|
||||
|
||||
# HTTP requests
|
||||
gem 'httparty', '~> 0.20.0'
|
||||
gem 'httparty', '~> 0.21.0'
|
||||
|
||||
# Colored output to console
|
||||
gem 'rainbow', '~> 3.0'
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@
|
|||
{"name":"http-accept","version":"1.7.0","platform":"ruby","checksum":"c626860682bfbb3b46462f8c39cd470fd7b0584f61b3cc9df5b2e9eb9972a126"},
|
||||
{"name":"http-cookie","version":"1.0.5","platform":"ruby","checksum":"73756d46c7dbdc7023deecdb8a171348ea95a1b99810b31cfe8b4fb4e9a6318f"},
|
||||
{"name":"http-form_data","version":"2.3.0","platform":"ruby","checksum":"cc4eeb1361d9876821e31d7b1cf0b68f1cf874b201d27903480479d86448a5f3"},
|
||||
{"name":"httparty","version":"0.20.0","platform":"ruby","checksum":"490d2a028a5accc611f1685d479d80ef80b129140d24a93c53c119f578614867"},
|
||||
{"name":"httparty","version":"0.21.0","platform":"ruby","checksum":"00ef7bf9a71f30a3bff88edeb5b16a34bea883ab67c246b3f0db2d6794fe1214"},
|
||||
{"name":"httpclient","version":"2.8.3","platform":"ruby","checksum":"2951e4991214464c3e92107e46438527d23048e634f3aee91c719e0bdfaebda6"},
|
||||
{"name":"i18n","version":"1.12.0","platform":"ruby","checksum":"91e3cc1b97616d308707eedee413d82ee021d751c918661fb82152793e64aced"},
|
||||
{"name":"i18n_data","version":"0.13.1","platform":"ruby","checksum":"e5aa99b09a69b463bb0443fc1f9540351a49f3d1541c5e91316bafa035c63f66"},
|
||||
|
|
|
|||
|
|
@ -867,8 +867,8 @@ GEM
|
|||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.3.0)
|
||||
httparty (0.20.0)
|
||||
mime-types (~> 3.0)
|
||||
httparty (0.21.0)
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.12.0)
|
||||
|
|
@ -1862,7 +1862,7 @@ DEPENDENCIES
|
|||
health_check (~> 3.0)
|
||||
html-pipeline (~> 2.14.3)
|
||||
html2text
|
||||
httparty (~> 0.20.0)
|
||||
httparty (~> 0.21.0)
|
||||
icalendar
|
||||
invisible_captcha (~> 2.0.0)
|
||||
ipaddr (~> 1.2.5)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import getMergeRequestPipelines from '~/pipelines/graphql/queries/get_merge_request_pipelines.query.graphql';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __ } from '~/locale';
|
||||
import { getQueryHeaders } from '~/pipelines/components/graph/utils';
|
||||
import { graphqlEtagMergeRequestPipelines } from '~/pipelines/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
},
|
||||
inject: ['graphqlPath', 'mergeRequestId', 'targetProjectFullPath'],
|
||||
data() {
|
||||
return {
|
||||
pipelines: [],
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
pipelines: {
|
||||
query: getMergeRequestPipelines,
|
||||
context() {
|
||||
return getQueryHeaders(this.graphqlResourceEtag);
|
||||
},
|
||||
pollInterval: 10000,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.targetProjectFullPath,
|
||||
mergeRequestIid: String(this.mergeRequestId),
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return data?.project?.mergeRequest?.pipelines?.nodes || [];
|
||||
},
|
||||
error() {
|
||||
createAlert({ message: this.$options.i18n.fetchError });
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
graphqlResourceEtag() {
|
||||
return graphqlEtagMergeRequestPipelines(this.graphqlPath, this.mergeRequestId);
|
||||
},
|
||||
isLoading() {
|
||||
return this.$apollo.queries.pipelines.loading;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
fetchError: __("There was an error fetching this merge request's pipelines."),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="gl-mt-3">
|
||||
<gl-loading-icon v-if="isLoading" size="lg" />
|
||||
<ul v-else>
|
||||
<li v-for="pipeline in pipelines" :key="pipeline.id">{{ pipeline.path }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -93,7 +93,11 @@ function mountPipelines() {
|
|||
const { mrWidgetData } = gl;
|
||||
const table = new Vue({
|
||||
components: {
|
||||
CommitPipelinesTable: () => import('~/commit/pipelines/pipelines_table.vue'),
|
||||
CommitPipelinesTable: () => {
|
||||
return gon.features.mrPipelinesGraphql
|
||||
? import('~/commit/pipelines/pipelines_table_wrapper.vue')
|
||||
: import('~/commit/pipelines/pipelines_table.vue');
|
||||
},
|
||||
},
|
||||
apolloProvider,
|
||||
provide: {
|
||||
|
|
@ -103,6 +107,8 @@ function mountPipelines() {
|
|||
fullPath: pipelineTableViewEl.dataset.fullPath,
|
||||
graphqlPath: pipelineTableViewEl.dataset.graphqlPath,
|
||||
manualActionsLimit: 50,
|
||||
mergeRequestId: mrWidgetData ? mrWidgetData.iid : null,
|
||||
sourceProjectFullPath: mrWidgetData?.source_project_full_path || '',
|
||||
withFailedJobsDetails: true,
|
||||
},
|
||||
render(createElement) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import { GlLoadingIcon } from '@gitlab/ui';
|
|||
import { createAlert } from '~/alert';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { getQueryHeaders } from '~/pipelines/components/graph/utils';
|
||||
import { graphqlEtagPipelinePath } from '~/pipelines/utils';
|
||||
import getPipelineFailedJobs from '../../../graphql/queries/get_pipeline_failed_jobs.query.graphql';
|
||||
import { graphqlEtagPipelinePath, sortJobsByStatus } from './utils';
|
||||
import { sortJobsByStatus } from './utils';
|
||||
import FailedJobDetails from './failed_job_details.vue';
|
||||
|
||||
const POLL_INTERVAL = 10000;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,3 @@ export const sortJobsByStatus = (jobs = []) => {
|
|||
return 1;
|
||||
});
|
||||
};
|
||||
|
||||
export const graphqlEtagPipelinePath = (graphqlPath, pipelineId) => {
|
||||
return `${graphqlPath}pipelines/id/${pipelineId}`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
query getMergeRequestPipelines($mergeRequestIid: String!, $fullPath: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
id
|
||||
mergeRequest(iid: $mergeRequestIid) {
|
||||
id
|
||||
pipelines {
|
||||
count
|
||||
nodes {
|
||||
id
|
||||
iid
|
||||
path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,3 +154,11 @@ export const getPipelineDefaultTab = (url) => {
|
|||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const graphqlEtagPipelinePath = (graphqlPath, pipelineId) => {
|
||||
return `${graphqlPath}pipelines/id/${pipelineId}`;
|
||||
};
|
||||
|
||||
export const graphqlEtagMergeRequestPipelines = (graphqlPath, mergeRequestId) => {
|
||||
return `${graphqlPath}merge_requests/id/${mergeRequestId}`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlBadge, GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlBadge, GlButton, GlModalDirective, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import {
|
||||
destroyUserCountsManager,
|
||||
|
|
@ -34,20 +34,21 @@ export default {
|
|||
),
|
||||
SuperSidebarToggle,
|
||||
BrandLogo,
|
||||
GlIcon,
|
||||
},
|
||||
i18n: {
|
||||
createNew: __('Create new...'),
|
||||
homepage: __('Homepage'),
|
||||
issues: __('Issues'),
|
||||
mergeRequests: __('Merge requests'),
|
||||
search: __('Search'),
|
||||
searchKbdHelp: sprintf(
|
||||
s__('GlobalSearch|Search GitLab %{kbdOpen}/%{kbdClose}'),
|
||||
s__('GlobalSearch|Type %{kbdOpen}/%{kbdClose} to search'),
|
||||
{ kbdOpen: '<kbd>', kbdClose: '</kbd>' },
|
||||
false,
|
||||
),
|
||||
todoList: __('To-Do list'),
|
||||
stopImpersonating: __('Stop impersonating'),
|
||||
searchBtnText: __('Search or go to…'),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
|
@ -128,17 +129,6 @@ export default {
|
|||
/>
|
||||
<create-menu v-if="sidebarData.is_logged_in" :groups="sidebarData.create_new_menu_groups" />
|
||||
|
||||
<gl-button
|
||||
id="super-sidebar-search"
|
||||
v-gl-tooltip.bottom.hover.noninteractive.ds500.html="searchTooltip"
|
||||
v-gl-modal="$options.SEARCH_MODAL_ID"
|
||||
data-testid="super-sidebar-search-button"
|
||||
icon="search"
|
||||
:aria-label="$options.i18n.search"
|
||||
category="tertiary"
|
||||
/>
|
||||
<search-modal @shown="hideSearchTooltip" @hidden="showSearchTooltip" />
|
||||
|
||||
<user-menu v-if="sidebarData.is_logged_in" :data="sidebarData" />
|
||||
|
||||
<gl-button
|
||||
|
|
@ -202,5 +192,18 @@ export default {
|
|||
data-track-property="nav_core_menu"
|
||||
/>
|
||||
</div>
|
||||
<div class="gl-px-3 gl-pt-2 gl-pb-3">
|
||||
<button
|
||||
id="super-sidebar-search"
|
||||
v-gl-tooltip.bottom.hover.noninteractive.ds500.html="searchTooltip"
|
||||
v-gl-modal="$options.SEARCH_MODAL_ID"
|
||||
class="counter gl-display-block gl-py-3 gl-bg-gray-10 gl-rounded-base gl-text-gray-900 gl-border-none gl-inset-border-1-gray-a-08 gl-line-height-1 gl-focus--focus gl-w-full"
|
||||
data-testid="super-sidebar-search-button"
|
||||
>
|
||||
<gl-icon name="search" />
|
||||
{{ $options.i18n.searchBtnText }}
|
||||
</button>
|
||||
<search-modal @shown="hideSearchTooltip" @hidden="showSearchTooltip" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:review_apps_redeploy_mr_widget, project)
|
||||
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
|
||||
push_frontend_feature_flag(:action_cable_notes, project)
|
||||
push_frontend_feature_flag(:mr_pipelines_graphql, project)
|
||||
end
|
||||
|
||||
before_action only: [:edit] do
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
module LooseForeignKeys
|
||||
class ModificationTracker
|
||||
MAX_DELETES = 100_000
|
||||
MAX_UPDATES = 50_000
|
||||
MAX_RUNTIME = 30.seconds # must be less than the scheduling frequency of the LooseForeignKeys::CleanupWorker cron worker
|
||||
|
||||
delegate :monotonic_time, to: :'Gitlab::Metrics::System'
|
||||
|
||||
def initialize
|
||||
|
|
@ -22,6 +18,18 @@ module LooseForeignKeys
|
|||
)
|
||||
end
|
||||
|
||||
def max_runtime
|
||||
30.seconds
|
||||
end
|
||||
|
||||
def max_deletes
|
||||
100_000
|
||||
end
|
||||
|
||||
def max_updates
|
||||
50_000
|
||||
end
|
||||
|
||||
def add_deletions(table, count)
|
||||
@delete_count_by_table[table] += count
|
||||
@deletes_counter.increment({ table: table }, count)
|
||||
|
|
@ -33,9 +41,9 @@ module LooseForeignKeys
|
|||
end
|
||||
|
||||
def over_limit?
|
||||
@delete_count_by_table.values.sum >= MAX_DELETES ||
|
||||
@update_count_by_table.values.sum >= MAX_UPDATES ||
|
||||
monotonic_time - @start_time >= MAX_RUNTIME
|
||||
@delete_count_by_table.values.sum >= max_deletes ||
|
||||
@update_count_by_table.values.sum >= max_updates ||
|
||||
monotonic_time - @start_time >= max_runtime
|
||||
end
|
||||
|
||||
def stats
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module LooseForeignKeys
|
||||
# This is a modification tracker with the additional limits that can be enabled
|
||||
# for some database via an OPS Feature Flag.
|
||||
|
||||
class TurboModificationTracker < ModificationTracker
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
override :max_runtime
|
||||
def max_runtime
|
||||
45.seconds
|
||||
end
|
||||
|
||||
override :max_deletes
|
||||
def max_deletes
|
||||
200_000
|
||||
end
|
||||
|
||||
override :max_updates
|
||||
def max_updates
|
||||
150_000
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,13 +4,13 @@ module LooseForeignKeys
|
|||
class ProcessDeletedRecordsService
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
def initialize(connection:)
|
||||
def initialize(connection:, modification_tracker: LooseForeignKeys::ModificationTracker.new)
|
||||
@connection = connection
|
||||
@modification_tracker = modification_tracker
|
||||
end
|
||||
|
||||
def execute
|
||||
raised_error = false
|
||||
modification_tracker = ModificationTracker.new
|
||||
tracked_tables.cycle do |table|
|
||||
records = load_batch_for_table(table)
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ module LooseForeignKeys
|
|||
|
||||
private
|
||||
|
||||
attr_reader :connection
|
||||
attr_reader :connection, :modification_tracker
|
||||
|
||||
def db_config_name
|
||||
::Gitlab::Database.db_config_name(connection)
|
||||
|
|
|
|||
|
|
@ -12,18 +12,23 @@ module LooseForeignKeys
|
|||
idempotent!
|
||||
|
||||
def perform
|
||||
connection_name, base_model = current_connection_name_and_base_model
|
||||
modification_tracker, turbo_mode = initialize_modification_tracker_for(connection_name)
|
||||
|
||||
# Add small buffer on MAX_RUNTIME to account for single long running
|
||||
# query or extra worker time after the cleanup.
|
||||
lock_ttl = ModificationTracker::MAX_RUNTIME + 20.seconds
|
||||
lock_ttl = modification_tracker.max_runtime + 10.seconds
|
||||
|
||||
in_lock(self.class.name.underscore, ttl: lock_ttl, retries: 0) do
|
||||
stats = {}
|
||||
|
||||
connection_name, base_model = current_connection_name_and_base_model
|
||||
|
||||
Gitlab::Database::SharedModel.using_connection(base_model.connection) do
|
||||
stats = ProcessDeletedRecordsService.new(connection: base_model.connection).execute
|
||||
stats = ProcessDeletedRecordsService.new(
|
||||
connection: base_model.connection,
|
||||
modification_tracker: modification_tracker
|
||||
).execute
|
||||
stats[:connection] = connection_name
|
||||
stats[:turbo_mode] = turbo_mode
|
||||
end
|
||||
|
||||
log_extra_metadata_on_done(:stats, stats)
|
||||
|
|
@ -41,5 +46,16 @@ module LooseForeignKeys
|
|||
connections_with_name = Gitlab::Database.database_base_models_with_gitlab_shared.to_a # this will never be empty
|
||||
connections_with_name[minutes_since_epoch % connections_with_name.count]
|
||||
end
|
||||
|
||||
def initialize_modification_tracker_for(connection_name)
|
||||
turbo_mode = turbo_mode?(connection_name)
|
||||
modification_tracker ||= turbo_mode ? TurboModificationTracker.new : ModificationTracker.new
|
||||
[modification_tracker, turbo_mode]
|
||||
end
|
||||
|
||||
def turbo_mode?(connection_name)
|
||||
%w[main ci].include?(connection_name) &&
|
||||
Feature.enabled?(:"loose_foreign_keys_turbo_mode_#{connection_name}", type: :ops)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: advanced_search_decrease_indexing_timeout
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111902
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/391570
|
||||
milestone: '15.9'
|
||||
name: mr_pipelines_graphql
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128929
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419726
|
||||
milestone: '16.4'
|
||||
type: development
|
||||
group: group::global search
|
||||
default_enabled: true
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: loose_foreign_keys_turbo_mode_ci
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128538
|
||||
rollout_issue_url:
|
||||
milestone: '16.4'
|
||||
type: ops
|
||||
group: group::tenant scale
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: loose_foreign_keys_turbo_mode_main
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128538
|
||||
rollout_issue_url:
|
||||
milestone: '16.4'
|
||||
type: ops
|
||||
group: group::tenant scale
|
||||
default_enabled: false
|
||||
|
|
@ -193,7 +193,7 @@ This new architecture allows GitLab to be resilient to connectivity issues betwe
|
|||
WARNING:
|
||||
This list of limitations only reflects the latest version of GitLab. If you are using an older version, extra limitations may be in place.
|
||||
|
||||
- Pushing directly to a **secondary** site redirects (for HTTP) or proxies (for SSH) the request to the **primary** site instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/-/issues/1381), except when using Git over HTTP with credentials embedded in the URI. For example, `https://user:password@secondary.tld`.
|
||||
- Pushing directly to a **secondary** site redirects (for HTTP) or proxies (for SSH) the request to the **primary** site instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/-/issues/1381). The limitation is that you cannot use Git over HTTP with credentials embedded in the URI, for example, `https://user:personal-access-token@secondary.tld`. For more information, see how to [use a Geo Site](replication/usage.md).
|
||||
- The **primary** site has to be online for OAuth login to happen. Existing sessions and Git are not affected. Support for the **secondary** site to use an OAuth provider independent from the primary is [being planned](https://gitlab.com/gitlab-org/gitlab/-/issues/208465).
|
||||
- The installation takes multiple manual steps that together can take about an hour depending on circumstances. Consider using [the GitLab Environment Toolkit](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) to deploy and operate production GitLab instances based on our [Reference Architectures](../reference_architectures/index.md), including automation of common daily tasks. We are planning to [improve Geo's installation even further](https://gitlab.com/groups/gitlab-org/-/epics/1465).
|
||||
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** site.
|
||||
|
|
|
|||
|
|
@ -339,6 +339,12 @@ It points to the commit you're rolling back to.
|
|||
For the rollback to succeed, the deployment process must be defined in
|
||||
the job's `script`.
|
||||
|
||||
Only the [deployment jobs](../jobs/index.md#deployment-jobs) are run.
|
||||
In cases where a previous job generates artifacts that must be regenerated
|
||||
on deploy, you must manually run the necessary jobs from the pipelines page.
|
||||
For example, if you use Terraform and your `plan` and `apply` commands are separated
|
||||
into multiple jobs, you must manually run the jobs to deploy or roll back.
|
||||
|
||||
#### Retry or roll back a deployment
|
||||
|
||||
If there is a problem with a deployment, you can retry it or roll it back.
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ To install `agentk`:
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: gitlab-agent-token
|
||||
name: gitlab-agent-token-initial
|
||||
type: Opaque
|
||||
stringData:
|
||||
values.yaml: |-
|
||||
|
|
@ -175,10 +175,12 @@ To install `agentk`:
|
|||
kasAddress: "wss://kas.gitlab.com"
|
||||
valuesFrom:
|
||||
- kind: Secret
|
||||
name: gitlab-agent-token
|
||||
name: gitlab-agent-token-initial
|
||||
valuesKey: values.yaml
|
||||
```
|
||||
|
||||
The Helm release creates a new secret with the name `gitlab-agent-token`, which is managed by Helm.
|
||||
|
||||
1. To verify that `agentk` is installed and running in the cluster, run the following command:
|
||||
|
||||
```shell
|
||||
|
|
|
|||
|
|
@ -21829,9 +21829,6 @@ msgstr ""
|
|||
msgid "GlobalSearch|Search GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Search GitLab %{kbdOpen}/%{kbdClose}"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Search for projects, issues, etc."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21862,6 +21859,9 @@ msgstr ""
|
|||
msgid "GlobalSearch|There was an error fetching the \"Syntax Options\" document."
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Type %{kbdOpen}/%{kbdClose} to search"
|
||||
msgstr ""
|
||||
|
||||
msgid "GlobalSearch|Type and press the enter key to submit search."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -41804,6 +41804,9 @@ msgstr ""
|
|||
msgid "Search or filter results…"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search or go to…"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search page"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47906,6 +47909,9 @@ msgstr ""
|
|||
msgid "There was an error fetching the variables."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching this merge request's pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching value stream analytics stages."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
# them to be overridden.
|
||||
#
|
||||
# For details on how to run this, see the documentation comments at the top of
|
||||
# qa/qa/specs/features/ee/browser_ui/3_create/remote_development/create_new_workspace_and_terminate_spec.rb
|
||||
# qa/qa/specs/features/ee/browser_ui/3_create/remote_development/with_prerequisite_done/workspace_actions_with_prerequisite_done_spec.rb
|
||||
|
||||
DEFAULT_PASSWORD='5iveL!fe'
|
||||
|
||||
|
|
@ -28,9 +28,8 @@ echo "TEST_INSTANCE_URL: ${TEST_INSTANCE_URL}"
|
|||
|
||||
working_directory="$(git rev-parse --show-toplevel)/qa"
|
||||
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/397005
|
||||
# Remove the '--tag quarantine' from below once this test is removed from quarantine
|
||||
# This test is currently quarantined as its only used for local testing, so we have to use the '--tag quarantine'
|
||||
(cd "$working_directory" && \
|
||||
bundle && \
|
||||
bundle exec bin/qa Test::Instance::All "$TEST_INSTANCE_URL" -- \
|
||||
--tag quarantine qa/specs/features/ee/browser_ui/3_create/remote_development/without_setup)
|
||||
--tag quarantine qa/specs/features/ee/browser_ui/3_create/remote_development/with_prerequisite_done/workspace_actions_with_prerequisite_done_spec.rb)
|
||||
|
|
|
|||
|
|
@ -48,60 +48,29 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
|||
end
|
||||
# rubocop:enable RSpec/AvoidConditionalStatements
|
||||
|
||||
context 'when a user created a merge request in the parent project' do
|
||||
let!(:merge_request) do
|
||||
create(
|
||||
:merge_request,
|
||||
source_project: project,
|
||||
target_project: project,
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master'
|
||||
)
|
||||
end
|
||||
|
||||
let!(:push_pipeline) do
|
||||
Ci::CreatePipelineService.new(project, user, ref: 'feature')
|
||||
.execute(:push)
|
||||
.payload
|
||||
end
|
||||
|
||||
let!(:detached_merge_request_pipeline) do
|
||||
Ci::CreatePipelineService.new(project, user, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
.payload
|
||||
end
|
||||
|
||||
context 'with feature flag `mr_pipelines_graphql turned off`' do
|
||||
before do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
stub_feature_flags(mr_pipelines_graphql: false)
|
||||
end
|
||||
|
||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-created"]', count: 2)
|
||||
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
context 'when a user created a merge request in the parent project' do
|
||||
let!(:merge_request) do
|
||||
create(
|
||||
:merge_request,
|
||||
source_project: project,
|
||||
target_project: project,
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline', :sidekiq_might_not_need_inline do
|
||||
click_link "Overview"
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user updated a merge request in the parent project', :sidekiq_might_not_need_inline do
|
||||
let!(:push_pipeline_2) do
|
||||
let!(:push_pipeline) do
|
||||
Ci::CreatePipelineService.new(project, user, ref: 'feature')
|
||||
.execute(:push)
|
||||
.payload
|
||||
end
|
||||
|
||||
let!(:detached_merge_request_pipeline_2) do
|
||||
let!(:detached_merge_request_pipeline) do
|
||||
Ci::CreatePipelineService.new(project, user, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
.payload
|
||||
|
|
@ -117,49 +86,186 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
|||
|
||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 4)
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[0])
|
||||
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[1])
|
||||
.to have_content("##{detached_merge_request_pipeline.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[2])
|
||||
.to have_content("##{push_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[3])
|
||||
.to have_content("##{push_pipeline.id}")
|
||||
expect(page).to have_selector('[data-testid="ci-badge-created"]', count: 2)
|
||||
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees detached tag for detached merge request pipelines' do
|
||||
page.within('.ci-table') do
|
||||
expect(all('.pipeline-tags')[0])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[1])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[2])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[3])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline' do
|
||||
click_link 'Overview'
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline', :sidekiq_might_not_need_inline do
|
||||
click_link "Overview"
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user updated a merge request in the parent project', :sidekiq_might_not_need_inline do
|
||||
let!(:push_pipeline_2) do
|
||||
Ci::CreatePipelineService.new(project, user, ref: 'feature')
|
||||
.execute(:push)
|
||||
.payload
|
||||
end
|
||||
|
||||
let!(:detached_merge_request_pipeline_2) do
|
||||
Ci::CreatePipelineService.new(project, user, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
.payload
|
||||
end
|
||||
|
||||
before do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 4)
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[0])
|
||||
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[1])
|
||||
.to have_content("##{detached_merge_request_pipeline.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[2])
|
||||
.to have_content("##{push_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[3])
|
||||
.to have_content("##{push_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees detached tag for detached merge request pipelines' do
|
||||
page.within('.ci-table') do
|
||||
expect(all('.pipeline-tags')[0])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[1])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[2])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[3])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline' do
|
||||
click_link 'Overview'
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user created a merge request in the parent project' do
|
||||
before do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user merges a merge request in the parent project', :sidekiq_might_not_need_inline do
|
||||
before do
|
||||
click_link 'Overview'
|
||||
click_button 'Set to auto-merge'
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
context 'when detached merge request pipeline is pending' do
|
||||
it 'waits the head pipeline' do
|
||||
expect(page).to have_content mr_widget_title
|
||||
expect(page).to have_button('Cancel auto-merge')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when branch pipeline succeeds' do
|
||||
before do
|
||||
click_link 'Overview'
|
||||
push_pipeline.reload.succeed!
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'waits the head pipeline' do
|
||||
expect(page).to have_content mr_widget_title
|
||||
expect(page).to have_button('Cancel auto-merge')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no `merge_requests` keyword in .gitlab-ci.yml' do
|
||||
let(:config) do
|
||||
{
|
||||
build: {
|
||||
script: 'build'
|
||||
},
|
||||
test: {
|
||||
script: 'test'
|
||||
},
|
||||
deploy: {
|
||||
script: 'deploy'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'sees a branch pipeline in pipeline tab' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-created"]', count: 1)
|
||||
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{push_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest branch pipeline as the head pipeline', :sidekiq_might_not_need_inline do
|
||||
click_link 'Overview'
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{push_pipeline.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user created a merge request in the parent project' do
|
||||
context 'when a user created a merge request from a forked project to the parent project', :sidekiq_might_not_need_inline do
|
||||
let(:merge_request) do
|
||||
create(
|
||||
:merge_request,
|
||||
source_project: forked_project,
|
||||
target_project: project,
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master'
|
||||
)
|
||||
end
|
||||
|
||||
let!(:push_pipeline) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:push)
|
||||
.payload
|
||||
end
|
||||
|
||||
let!(:detached_merge_request_pipeline) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
.payload
|
||||
end
|
||||
|
||||
let(:forked_project) { fork_project(project, user2, repository: true) }
|
||||
let(:user2) { create(:user) }
|
||||
|
||||
before do
|
||||
forked_project.add_maintainer(user2)
|
||||
|
||||
stub_feature_flags(auto_merge_labels_mr_widget: false)
|
||||
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
|
|
@ -167,9 +273,133 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
|||
end
|
||||
end
|
||||
|
||||
context 'when a user merges a merge request in the parent project', :sidekiq_might_not_need_inline do
|
||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 2)
|
||||
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline' do
|
||||
click_link "Overview"
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees pipeline list in forked project' do
|
||||
visit project_pipelines_path(forked_project)
|
||||
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 2)
|
||||
end
|
||||
|
||||
context 'when a user updated a merge request from a forked project to the parent project' do
|
||||
let!(:push_pipeline_2) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:push)
|
||||
.payload
|
||||
end
|
||||
|
||||
let!(:detached_merge_request_pipeline_2) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
.payload
|
||||
end
|
||||
|
||||
before do
|
||||
click_link 'Overview'
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 4)
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[0])
|
||||
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[1])
|
||||
.to have_content("##{detached_merge_request_pipeline.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[2])
|
||||
.to have_content("##{push_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[3])
|
||||
.to have_content("##{push_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees detached tag for detached merge request pipelines' do
|
||||
page.within('.ci-table') do
|
||||
expect(all('.pipeline-tags')[0])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[1])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[2])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[3])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline' do
|
||||
click_link "Overview"
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees pipeline list in forked project' do
|
||||
visit project_pipelines_path(forked_project)
|
||||
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the latest pipeline is running in the parent project' do
|
||||
before do
|
||||
create(:ci_pipeline,
|
||||
source: :merge_request_event,
|
||||
project: project,
|
||||
ref: 'feature',
|
||||
sha: merge_request.diff_head_sha,
|
||||
user: user,
|
||||
merge_request: merge_request,
|
||||
status: :running)
|
||||
merge_request.update_head_pipeline
|
||||
end
|
||||
|
||||
context 'when the previous pipeline failed in the fork project' do
|
||||
before do
|
||||
detached_merge_request_pipeline.reload.drop!
|
||||
end
|
||||
|
||||
context 'when the parent project enables pipeline must succeed' do
|
||||
before do
|
||||
project.update!(only_allow_merge_if_pipeline_succeeds: true)
|
||||
end
|
||||
|
||||
it 'shows Set to auto-merge button' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
expect(page).to have_button('Set to auto-merge')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user merges a merge request from a forked project to the parent project' do
|
||||
before do
|
||||
click_link("Overview")
|
||||
|
||||
click_button 'Set to auto-merge'
|
||||
|
||||
wait_for_requests
|
||||
|
|
@ -182,9 +412,21 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
|||
end
|
||||
end
|
||||
|
||||
context 'when detached merge request pipeline succeeds' do
|
||||
before do
|
||||
detached_merge_request_pipeline.reload.succeed!
|
||||
|
||||
refresh
|
||||
end
|
||||
|
||||
it 'merges the merge request' do
|
||||
expect(page).to have_content('Merged by')
|
||||
expect(page).to have_button('Revert')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when branch pipeline succeeds' do
|
||||
before do
|
||||
click_link 'Overview'
|
||||
push_pipeline.reload.succeed!
|
||||
|
||||
wait_for_requests
|
||||
|
|
@ -197,241 +439,5 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no `merge_requests` keyword in .gitlab-ci.yml' do
|
||||
let(:config) do
|
||||
{
|
||||
build: {
|
||||
script: 'build'
|
||||
},
|
||||
test: {
|
||||
script: 'test'
|
||||
},
|
||||
deploy: {
|
||||
script: 'deploy'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'sees a branch pipeline in pipeline tab' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-created"]', count: 1)
|
||||
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{push_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest branch pipeline as the head pipeline', :sidekiq_might_not_need_inline do
|
||||
click_link 'Overview'
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{push_pipeline.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user created a merge request from a forked project to the parent project', :sidekiq_might_not_need_inline do
|
||||
let(:merge_request) do
|
||||
create(
|
||||
:merge_request,
|
||||
source_project: forked_project,
|
||||
target_project: project,
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master'
|
||||
)
|
||||
end
|
||||
|
||||
let!(:push_pipeline) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:push)
|
||||
.payload
|
||||
end
|
||||
|
||||
let!(:detached_merge_request_pipeline) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
.payload
|
||||
end
|
||||
|
||||
let(:forked_project) { fork_project(project, user2, repository: true) }
|
||||
let(:user2) { create(:user) }
|
||||
|
||||
before do
|
||||
forked_project.add_maintainer(user2)
|
||||
|
||||
stub_feature_flags(auto_merge_labels_mr_widget: false)
|
||||
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 2)
|
||||
expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline' do
|
||||
click_link "Overview"
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees pipeline list in forked project' do
|
||||
visit project_pipelines_path(forked_project)
|
||||
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 2)
|
||||
end
|
||||
|
||||
context 'when a user updated a merge request from a forked project to the parent project' do
|
||||
let!(:push_pipeline_2) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:push)
|
||||
.payload
|
||||
end
|
||||
|
||||
let!(:detached_merge_request_pipeline_2) do
|
||||
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
.payload
|
||||
end
|
||||
|
||||
before do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees branch pipelines and detached merge request pipelines in correct order' do
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 4)
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[0])
|
||||
.to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[1])
|
||||
.to have_content("##{detached_merge_request_pipeline.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[2])
|
||||
.to have_content("##{push_pipeline_2.id}")
|
||||
|
||||
expect(all('[data-testid="pipeline-url-link"]')[3])
|
||||
.to have_content("##{push_pipeline.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees detached tag for detached merge request pipelines' do
|
||||
page.within('.ci-table') do
|
||||
expect(all('.pipeline-tags')[0])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[1])
|
||||
.to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[2])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
|
||||
expect(all('.pipeline-tags')[3])
|
||||
.not_to have_content(expected_detached_mr_tag)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees the latest detached merge request pipeline as the head pipeline' do
|
||||
click_link "Overview"
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'sees pipeline list in forked project' do
|
||||
visit project_pipelines_path(forked_project)
|
||||
|
||||
expect(page).to have_selector('[data-testid="ci-badge-pending"]', count: 4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the latest pipeline is running in the parent project' do
|
||||
before do
|
||||
create(:ci_pipeline,
|
||||
source: :merge_request_event,
|
||||
project: project,
|
||||
ref: 'feature',
|
||||
sha: merge_request.diff_head_sha,
|
||||
user: user,
|
||||
merge_request: merge_request,
|
||||
status: :running)
|
||||
merge_request.update_head_pipeline
|
||||
end
|
||||
|
||||
context 'when the previous pipeline failed in the fork project' do
|
||||
before do
|
||||
detached_merge_request_pipeline.reload.drop!
|
||||
end
|
||||
|
||||
context 'when the parent project enables pipeline must succeed' do
|
||||
before do
|
||||
project.update!(only_allow_merge_if_pipeline_succeeds: true)
|
||||
end
|
||||
|
||||
it 'shows Set to auto-merge button' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
expect(page).to have_button('Set to auto-merge')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user merges a merge request from a forked project to the parent project' do
|
||||
before do
|
||||
click_link("Overview")
|
||||
|
||||
click_button 'Set to auto-merge'
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
context 'when detached merge request pipeline is pending' do
|
||||
it 'waits the head pipeline' do
|
||||
expect(page).to have_content mr_widget_title
|
||||
expect(page).to have_button('Cancel auto-merge')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when detached merge request pipeline succeeds' do
|
||||
before do
|
||||
detached_merge_request_pipeline.reload.succeed!
|
||||
|
||||
refresh
|
||||
end
|
||||
|
||||
it 'merges the merge request' do
|
||||
expect(page).to have_content('Merged by')
|
||||
expect(page).to have_button('Revert')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when branch pipeline succeeds' do
|
||||
before do
|
||||
push_pipeline.reload.succeed!
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'waits the head pipeline' do
|
||||
expect(page).to have_content mr_widget_title
|
||||
expect(page).to have_button('Cancel auto-merge')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,15 +30,33 @@ RSpec.describe 'Merge request > User sees pipelines from forked project', :js,
|
|||
before do
|
||||
create(:ci_build, pipeline: pipeline, name: 'rspec')
|
||||
create(:ci_build, pipeline: pipeline, name: 'spinach')
|
||||
|
||||
sign_in(user)
|
||||
visit project_merge_request_path(target_project, merge_request)
|
||||
end
|
||||
|
||||
it 'user visits a pipelines page', :sidekiq_might_not_need_inline do
|
||||
page.within('.merge-request-tabs') { click_link 'Pipelines' }
|
||||
context 'with feature flag `mr_pipelines_graphql` turned off' do
|
||||
before do
|
||||
stub_feature_flags(mr_pipelines_graphql: false)
|
||||
visit project_merge_request_path(target_project, merge_request)
|
||||
end
|
||||
|
||||
it 'user visits a pipelines page', :sidekiq_might_not_need_inline do
|
||||
page.within('.merge-request-tabs') { click_link 'Pipelines' }
|
||||
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_content(pipeline.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag `mr_pipelines_graphql` turned on' do
|
||||
before do
|
||||
stub_feature_flags(mr_pipelines_graphql: true)
|
||||
visit project_merge_request_path(target_project, merge_request)
|
||||
end
|
||||
|
||||
it 'user visits a pipelines page', :sidekiq_might_not_need_inline do
|
||||
page.within('.merge-request-tabs') { click_link 'Pipelines' }
|
||||
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_content(pipeline.id)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,285 +3,291 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Merge request > User sees pipelines', :js, feature_category: :code_review_workflow do
|
||||
describe 'pipeline tab' do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.target_project }
|
||||
let(:user) { project.creator }
|
||||
|
||||
context 'with feature flag `mr_pipelines_graphql turned off`' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
stub_feature_flags(mr_pipelines_graphql: false)
|
||||
end
|
||||
|
||||
context 'with pipelines' do
|
||||
let!(:pipeline) do
|
||||
describe 'pipeline tab' do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.target_project }
|
||||
let(:user) { project.creator }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'with pipelines' do
|
||||
let!(:pipeline) do
|
||||
create(
|
||||
:ci_pipeline,
|
||||
:success,
|
||||
project: merge_request.source_project,
|
||||
ref: merge_request.source_branch,
|
||||
sha: merge_request.diff_head_sha
|
||||
)
|
||||
end
|
||||
|
||||
let!(:manual_job) { create(:ci_build, :manual, name: 'job1', stage: 'deploy', pipeline: pipeline) }
|
||||
|
||||
let!(:job) { create(:ci_build, :success, name: 'job2', stage: 'test', pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
|
||||
end
|
||||
|
||||
it 'pipelines table displays correctly' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
expect(page.find('.ci-widget')).to have_content('passed')
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within(find('[data-testid="pipeline-table-row"]', match: :first)) do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-passed"]')
|
||||
expect(page).to have_content(pipeline.id)
|
||||
expect(page).to have_content('API')
|
||||
expect(page).to have_css('[data-testid="pipeline-mini-graph"]')
|
||||
expect(page).to have_css('[data-testid="pipelines-manual-actions-dropdown"]')
|
||||
expect(page).to have_css('[data-testid="pipeline-multi-actions-dropdown"]')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a detached merge request pipeline' do
|
||||
let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
|
||||
|
||||
it 'displays the "Run pipeline" button' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a merged results pipeline' do
|
||||
let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
|
||||
|
||||
it 'displays the "Run pipeline" button' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without pipelines' do
|
||||
before do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
end
|
||||
|
||||
it 'user visits merge request page' do
|
||||
page.within('.merge-request-tabs') do
|
||||
expect(page).to have_link('Pipelines')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows empty state with run pipeline button' do
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
|
||||
expect(page).to have_content('There are currently no pipelines.')
|
||||
expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'fork MRs in parent project', :sidekiq_inline do
|
||||
include ProjectForksHelper
|
||||
|
||||
let_it_be(:parent_project) { create(:project, :public, :repository) }
|
||||
let_it_be(:forked_project) { fork_project(parent_project, developer_in_fork, repository: true, target_project: create(:project, :public, :repository)) }
|
||||
let_it_be(:developer_in_parent) { create(:user) }
|
||||
let_it_be(:developer_in_fork) { create(:user) }
|
||||
let_it_be(:reporter_in_parent_and_developer_in_fork) { create(:user) }
|
||||
|
||||
let(:merge_request) do
|
||||
create(
|
||||
:ci_pipeline,
|
||||
:success,
|
||||
project: merge_request.source_project,
|
||||
ref: merge_request.source_branch,
|
||||
sha: merge_request.diff_head_sha
|
||||
:merge_request,
|
||||
:with_detached_merge_request_pipeline,
|
||||
source_project: forked_project,
|
||||
source_branch: 'feature',
|
||||
target_project: parent_project,
|
||||
target_branch: 'master'
|
||||
)
|
||||
end
|
||||
|
||||
let!(:manual_job) { create(:ci_build, :manual, name: 'job1', stage: 'deploy', pipeline: pipeline) }
|
||||
let(:config) do
|
||||
{ test: { script: 'test', rules: [{ if: '$CI_MERGE_REQUEST_ID' }] } }
|
||||
end
|
||||
|
||||
let!(:job) { create(:ci_build, :success, name: 'job2', stage: 'test', pipeline: pipeline) }
|
||||
before_all do
|
||||
parent_project.add_developer(developer_in_parent)
|
||||
parent_project.add_reporter(reporter_in_parent_and_developer_in_fork)
|
||||
forked_project.add_developer(developer_in_fork)
|
||||
forked_project.add_developer(reporter_in_parent_and_developer_in_fork)
|
||||
end
|
||||
|
||||
before do
|
||||
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
sign_in(actor)
|
||||
end
|
||||
|
||||
it 'pipelines table displays correctly' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
after do
|
||||
parent_project.all_pipelines.delete_all
|
||||
forked_project.all_pipelines.delete_all
|
||||
end
|
||||
|
||||
expect(page.find('.ci-widget')).to have_content('passed')
|
||||
context 'when actor is a developer in parent project' do
|
||||
let(:actor) { developer_in_parent }
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
it 'creates a pipeline in the parent project when user proceeds with the warning' do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
create_merge_request_pipeline
|
||||
act_on_security_warning(action: 'Run pipeline')
|
||||
|
||||
check_pipeline(expected_project: parent_project)
|
||||
check_head_pipeline(expected_project: parent_project)
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
it 'does not create a pipeline in the parent project when user cancels the action', :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
page.within(find('[data-testid="pipeline-table-row"]', match: :first)) do
|
||||
expect(page).to have_selector('[data-testid="ci-badge-passed"]')
|
||||
expect(page).to have_content(pipeline.id)
|
||||
expect(page).to have_content('API')
|
||||
expect(page).to have_css('[data-testid="pipeline-mini-graph"]')
|
||||
expect(page).to have_css('[data-testid="pipelines-manual-actions-dropdown"]')
|
||||
expect(page).to have_css('[data-testid="pipeline-multi-actions-dropdown"]')
|
||||
create_merge_request_pipeline
|
||||
act_on_security_warning(action: 'Cancel')
|
||||
|
||||
check_no_new_pipeline_created
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a detached merge request pipeline' do
|
||||
let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
|
||||
context 'when actor is a developer in fork project' do
|
||||
let(:actor) { developer_in_fork }
|
||||
|
||||
it 'displays the "Run pipeline" button' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
it 'creates a pipeline in the fork project' do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
create_merge_request_pipeline
|
||||
|
||||
check_pipeline(expected_project: forked_project)
|
||||
check_head_pipeline(expected_project: forked_project)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when actor is a reporter in parent project and a developer in fork project' do
|
||||
let(:actor) { reporter_in_parent_and_developer_in_fork }
|
||||
|
||||
it 'creates a pipeline in the fork project' do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
create_merge_request_pipeline
|
||||
|
||||
check_pipeline(expected_project: forked_project)
|
||||
check_head_pipeline(expected_project: forked_project)
|
||||
end
|
||||
end
|
||||
|
||||
def create_merge_request_pipeline
|
||||
page.within('.merge-request-tabs') { click_link('Pipelines') }
|
||||
click_on('Run pipeline')
|
||||
end
|
||||
|
||||
def check_pipeline(expected_project:)
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="pipeline-table-row"]', count: 4)
|
||||
|
||||
page.within(first('[data-testid="pipeline-table-row"]')) do
|
||||
page.within('.pipeline-tags') do
|
||||
expect(page.find('[data-testid="pipeline-url-link"]')[:href]).to include(expected_project.full_path)
|
||||
expect(page).to have_content('merge request')
|
||||
end
|
||||
page.within('.pipeline-triggerer') do
|
||||
expect(page).to have_link(href: user_path(actor))
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a merged results pipeline' do
|
||||
let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
|
||||
def check_head_pipeline(expected_project:)
|
||||
page.within('.merge-request-tabs') { click_link('Overview') }
|
||||
|
||||
it 'displays the "Run pipeline" button' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page.find('.pipeline-id')[:href]).to include(expected_project.full_path)
|
||||
end
|
||||
end
|
||||
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
def act_on_security_warning(action:)
|
||||
page.within('#create-pipeline-for-fork-merge-request-modal') do
|
||||
expect(page).to have_content('Are you sure you want to run this pipeline?')
|
||||
click_button(action)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline')
|
||||
def check_no_new_pipeline_created
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="pipeline-table-row"]', count: 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without pipelines' do
|
||||
describe 'race condition' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
let(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
|
||||
|
||||
let(:merge_request_params) do
|
||||
{ "source_branch" => "feature", "source_project_id" => project.id,
|
||||
"target_branch" => "master", "target_project_id" => project.id, "title" => "A" }
|
||||
end
|
||||
|
||||
before do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
project.add_maintainer(user)
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it 'user visits merge request page' do
|
||||
page.within('.merge-request-tabs') do
|
||||
expect(page).to have_link('Pipelines')
|
||||
end
|
||||
end
|
||||
context 'when pipeline and merge request were created simultaneously', :delete do
|
||||
before do
|
||||
stub_ci_pipeline_to_return_yaml_file
|
||||
|
||||
it 'shows empty state with run pipeline button' do
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
threads = []
|
||||
|
||||
expect(page).to have_content('There are currently no pipelines.')
|
||||
expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'fork MRs in parent project', :sidekiq_inline do
|
||||
include ProjectForksHelper
|
||||
|
||||
let_it_be(:parent_project) { create(:project, :public, :repository) }
|
||||
let_it_be(:forked_project) { fork_project(parent_project, developer_in_fork, repository: true, target_project: create(:project, :public, :repository)) }
|
||||
let_it_be(:developer_in_parent) { create(:user) }
|
||||
let_it_be(:developer_in_fork) { create(:user) }
|
||||
let_it_be(:reporter_in_parent_and_developer_in_fork) { create(:user) }
|
||||
|
||||
let(:merge_request) do
|
||||
create(
|
||||
:merge_request,
|
||||
:with_detached_merge_request_pipeline,
|
||||
source_project: forked_project,
|
||||
source_branch: 'feature',
|
||||
target_project: parent_project,
|
||||
target_branch: 'master'
|
||||
)
|
||||
end
|
||||
|
||||
let(:config) do
|
||||
{ test: { script: 'test', rules: [{ if: '$CI_MERGE_REQUEST_ID' }] } }
|
||||
end
|
||||
|
||||
before_all do
|
||||
parent_project.add_developer(developer_in_parent)
|
||||
parent_project.add_reporter(reporter_in_parent_and_developer_in_fork)
|
||||
forked_project.add_developer(developer_in_fork)
|
||||
forked_project.add_developer(reporter_in_parent_and_developer_in_fork)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
sign_in(actor)
|
||||
end
|
||||
|
||||
after do
|
||||
parent_project.all_pipelines.delete_all
|
||||
forked_project.all_pipelines.delete_all
|
||||
end
|
||||
|
||||
context 'when actor is a developer in parent project' do
|
||||
let(:actor) { developer_in_parent }
|
||||
|
||||
it 'creates a pipeline in the parent project when user proceeds with the warning' do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
create_merge_request_pipeline
|
||||
act_on_security_warning(action: 'Run pipeline')
|
||||
|
||||
check_pipeline(expected_project: parent_project)
|
||||
check_head_pipeline(expected_project: parent_project)
|
||||
end
|
||||
|
||||
it 'does not create a pipeline in the parent project when user cancels the action', :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
create_merge_request_pipeline
|
||||
act_on_security_warning(action: 'Cancel')
|
||||
|
||||
check_no_new_pipeline_created
|
||||
end
|
||||
end
|
||||
|
||||
context 'when actor is a developer in fork project' do
|
||||
let(:actor) { developer_in_fork }
|
||||
|
||||
it 'creates a pipeline in the fork project' do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
create_merge_request_pipeline
|
||||
|
||||
check_pipeline(expected_project: forked_project)
|
||||
check_head_pipeline(expected_project: forked_project)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when actor is a reporter in parent project and a developer in fork project' do
|
||||
let(:actor) { reporter_in_parent_and_developer_in_fork }
|
||||
|
||||
it 'creates a pipeline in the fork project' do
|
||||
visit project_merge_request_path(parent_project, merge_request)
|
||||
|
||||
create_merge_request_pipeline
|
||||
|
||||
check_pipeline(expected_project: forked_project)
|
||||
check_head_pipeline(expected_project: forked_project)
|
||||
end
|
||||
end
|
||||
|
||||
def create_merge_request_pipeline
|
||||
page.within('.merge-request-tabs') { click_link('Pipelines') }
|
||||
click_on('Run pipeline')
|
||||
end
|
||||
|
||||
def check_pipeline(expected_project:)
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="pipeline-table-row"]', count: 4)
|
||||
|
||||
page.within(first('[data-testid="pipeline-table-row"]')) do
|
||||
page.within('.pipeline-tags') do
|
||||
expect(page.find('[data-testid="pipeline-url-link"]')[:href]).to include(expected_project.full_path)
|
||||
expect(page).to have_content('merge request')
|
||||
threads << Thread.new do
|
||||
Sidekiq::Worker.skipping_transaction_check do
|
||||
@merge_request = MergeRequests::CreateService.new(project: project, current_user: user, params: merge_request_params).execute
|
||||
end
|
||||
end
|
||||
page.within('.pipeline-triggerer') do
|
||||
expect(page).to have_link(href: user_path(actor))
|
||||
|
||||
threads << Thread.new do
|
||||
Sidekiq::Worker.skipping_transaction_check do
|
||||
@pipeline = Ci::CreatePipelineService.new(project, user, build_push_data).execute(:push).payload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_head_pipeline(expected_project:)
|
||||
page.within('.merge-request-tabs') { click_link('Overview') }
|
||||
|
||||
page.within('.ci-widget-content') do
|
||||
expect(page.find('.pipeline-id')[:href]).to include(expected_project.full_path)
|
||||
end
|
||||
end
|
||||
|
||||
def act_on_security_warning(action:)
|
||||
page.within('#create-pipeline-for-fork-merge-request-modal') do
|
||||
expect(page).to have_content('Are you sure you want to run this pipeline?')
|
||||
click_button(action)
|
||||
end
|
||||
end
|
||||
|
||||
def check_no_new_pipeline_created
|
||||
page.within('.ci-table') do
|
||||
expect(page).to have_selector('[data-testid="pipeline-table-row"]', count: 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'race condition' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
let(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
|
||||
|
||||
let(:merge_request_params) do
|
||||
{ "source_branch" => "feature", "source_project_id" => project.id,
|
||||
"target_branch" => "master", "target_project_id" => project.id, "title" => "A" }
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in user
|
||||
end
|
||||
|
||||
context 'when pipeline and merge request were created simultaneously', :delete do
|
||||
before do
|
||||
stub_ci_pipeline_to_return_yaml_file
|
||||
|
||||
threads = []
|
||||
|
||||
threads << Thread.new do
|
||||
Sidekiq::Worker.skipping_transaction_check do
|
||||
@merge_request = MergeRequests::CreateService.new(project: project, current_user: user, params: merge_request_params).execute
|
||||
end
|
||||
threads.each { |thr| thr.join }
|
||||
end
|
||||
|
||||
threads << Thread.new do
|
||||
Sidekiq::Worker.skipping_transaction_check do
|
||||
@pipeline = Ci::CreatePipelineService.new(project, user, build_push_data).execute(:push).payload
|
||||
end
|
||||
it 'user sees pipeline in merge request widget', :sidekiq_might_not_need_inline do
|
||||
visit project_merge_request_path(project, @merge_request)
|
||||
|
||||
expect(page.find(".ci-widget")).to have_content(TestEnv::BRANCH_SHA['feature'])
|
||||
expect(page.find(".ci-widget")).to have_content("##{@pipeline.id}")
|
||||
end
|
||||
|
||||
threads.each { |thr| thr.join }
|
||||
end
|
||||
|
||||
it 'user sees pipeline in merge request widget', :sidekiq_might_not_need_inline do
|
||||
visit project_merge_request_path(project, @merge_request)
|
||||
|
||||
expect(page.find(".ci-widget")).to have_content(TestEnv::BRANCH_SHA['feature'])
|
||||
expect(page.find(".ci-widget")).to have_content("##{@pipeline.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -291,3 +291,34 @@ export const refsListPropsMock = {
|
|||
urlPart: '/some/project/-/commits/',
|
||||
refType: 'heads',
|
||||
};
|
||||
|
||||
const createMergeRequestPipelines = (count = 30) => {
|
||||
const pipelines = [];
|
||||
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
pipelines.push({
|
||||
id: i,
|
||||
iid: i + 10,
|
||||
path: `/project/pipelines/${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
nodes: pipelines,
|
||||
};
|
||||
};
|
||||
|
||||
export const mergeRequestPipelinesResponse = {
|
||||
data: {
|
||||
project: {
|
||||
__typename: 'Project',
|
||||
id: 'gid://gitlab/Project/1',
|
||||
mergeRequest: {
|
||||
__typename: 'MergeRequest',
|
||||
id: 'gid://gitlab/MergeRequest/1',
|
||||
pipelines: createMergeRequestPipelines(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
import { createAlert } from '~/alert';
|
||||
import PipelinesTableWrapper from '~/commit/pipelines/pipelines_table_wrapper.vue';
|
||||
import getMergeRequestsPipelines from '~/pipelines/graphql/queries/get_merge_request_pipelines.query.graphql';
|
||||
|
||||
import { mergeRequestPipelinesResponse } from '../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
jest.mock('~/alert');
|
||||
|
||||
const pipelinesLength = mergeRequestPipelinesResponse.data.project.mergeRequest.pipelines.count;
|
||||
|
||||
let wrapper;
|
||||
let mergeRequestPipelinesRequest;
|
||||
let apolloMock;
|
||||
|
||||
const defaultProvide = {
|
||||
graphqlPath: '/api/graphql/',
|
||||
mergeRequestId: 1,
|
||||
targetProjectFullPath: '/group/project',
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
const handlers = [[getMergeRequestsPipelines, mergeRequestPipelinesRequest]];
|
||||
|
||||
apolloMock = createMockApollo(handlers);
|
||||
|
||||
wrapper = shallowMount(PipelinesTableWrapper, {
|
||||
apolloProvider: apolloMock,
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
},
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findPipelineList = () => wrapper.findAll('li');
|
||||
|
||||
beforeEach(() => {
|
||||
mergeRequestPipelinesRequest = jest.fn();
|
||||
mergeRequestPipelinesRequest.mockResolvedValue(mergeRequestPipelinesResponse);
|
||||
});
|
||||
afterEach(() => {
|
||||
apolloMock = null;
|
||||
createAlert.mockClear();
|
||||
});
|
||||
|
||||
describe('PipelinesTableWrapper component', () => {
|
||||
describe('When queries are loading', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders the loading icon', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render the pipeline list', () => {
|
||||
expect(findPipelineList()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When there is an error fetching pipelines', () => {
|
||||
beforeEach(async () => {
|
||||
mergeRequestPipelinesRequest.mockRejectedValueOnce({ error: 'API error message' });
|
||||
await createComponent();
|
||||
});
|
||||
it('shows an error message', () => {
|
||||
expect(createAlert).toHaveBeenCalledTimes(1);
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: "There was an error fetching this merge request's pipelines.",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('When queries have loaded', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('does not render the loading icon', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders a pipeline list', () => {
|
||||
expect(findPipelineList()).toHaveLength(pipelinesLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe('polling', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('polls every 10 seconds', () => {
|
||||
expect(mergeRequestPipelinesRequest).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
expect(mergeRequestPipelinesRequest).toHaveBeenCalledTimes(1);
|
||||
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
expect(mergeRequestPipelinesRequest).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -165,7 +165,7 @@ describe('UserBar component', () => {
|
|||
|
||||
it('search button should have tooltip', () => {
|
||||
const tooltip = getBinding(findSearchButton().element, 'gl-tooltip');
|
||||
expect(tooltip.value).toBe(`Search GitLab <kbd>/</kbd>`);
|
||||
expect(tooltip.value).toBe(`Type <kbd>/</kbd> to search`);
|
||||
});
|
||||
|
||||
it('should render search modal', () => {
|
||||
|
|
@ -184,7 +184,7 @@ describe('UserBar component', () => {
|
|||
findSearchModal().vm.$emit('hidden');
|
||||
await nextTick();
|
||||
const tooltip = getBinding(findSearchButton().element, 'gl-tooltip');
|
||||
expect(tooltip.value).toBe(`Search GitLab <kbd>/</kbd>`);
|
||||
expect(tooltip.value).toBe(`Type <kbd>/</kbd> to search`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -35,11 +35,14 @@ RSpec.describe Gitlab::HTTP do
|
|||
super do |response|
|
||||
response.instance_eval do
|
||||
def read_body(*)
|
||||
@body.each do |fragment|
|
||||
mock_stream = @body.split(' ')
|
||||
mock_stream.each do |fragment|
|
||||
sleep 0.002.seconds
|
||||
|
||||
yield fragment if block_given?
|
||||
end
|
||||
|
||||
@body
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -64,8 +67,8 @@ RSpec.describe Gitlab::HTTP do
|
|||
before do
|
||||
stub_const("#{described_class}::DEFAULT_READ_TOTAL_TIMEOUT", 0.001.seconds)
|
||||
|
||||
WebMock.stub_request(:post, /.*/).to_return do |request|
|
||||
{ body: %w(a b), status: 200 }
|
||||
WebMock.stub_request(:post, /.*/).to_return do
|
||||
{ body: "chunk-1 chunk-2", status: 200 }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe LooseForeignKeys::ModificationTracker do
|
||||
RSpec.describe LooseForeignKeys::ModificationTracker, feature_category: :database do
|
||||
subject(:tracker) { described_class.new }
|
||||
|
||||
describe '#over_limit?' do
|
||||
it 'is true when deletion MAX_DELETES is exceeded' do
|
||||
stub_const('LooseForeignKeys::ModificationTracker::MAX_DELETES', 5)
|
||||
it 'is true when deletion max_deletes is exceeded' do
|
||||
expect(tracker).to receive(:max_deletes).and_return(5)
|
||||
|
||||
tracker.add_deletions('issues', 10)
|
||||
expect(tracker).to be_over_limit
|
||||
|
|
@ -20,7 +20,7 @@ RSpec.describe LooseForeignKeys::ModificationTracker do
|
|||
end
|
||||
|
||||
it 'is true when deletion MAX_UPDATES is exceeded' do
|
||||
stub_const('LooseForeignKeys::ModificationTracker::MAX_UPDATES', 5)
|
||||
expect(tracker).to receive(:max_updates).and_return(5)
|
||||
|
||||
tracker.add_updates('issues', 3)
|
||||
tracker.add_updates('issues', 4)
|
||||
|
|
@ -36,9 +36,11 @@ RSpec.describe LooseForeignKeys::ModificationTracker do
|
|||
|
||||
it 'is true when max runtime is exceeded' do
|
||||
monotonic_time_before = 1 # this will be the start time
|
||||
monotonic_time_after = described_class::MAX_RUNTIME.to_i + 1 # this will be returned when over_limit? is called
|
||||
monotonic_time_after = 31 # this will be returned when over_limit? is called
|
||||
|
||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
|
||||
expect(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(
|
||||
monotonic_time_before, monotonic_time_after
|
||||
)
|
||||
|
||||
tracker
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe LooseForeignKeys::TurboModificationTracker, feature_category: :database do
|
||||
subject(:tracker) { described_class.new }
|
||||
|
||||
let(:normal_tracker) { LooseForeignKeys::ModificationTracker.new }
|
||||
|
||||
context 'with limits should be higher than LooseForeignKeys::ModificationTracker' do
|
||||
it 'expect max_deletes to be equal or higher' do
|
||||
expect(tracker.max_deletes).to be >= normal_tracker.max_deletes
|
||||
end
|
||||
|
||||
it 'expect max_updates to be equal or higher' do
|
||||
expect(tracker.max_updates).to be >= normal_tracker.max_updates
|
||||
end
|
||||
|
||||
it 'expect max_runtime to be equal or higher' do
|
||||
expect(tracker.max_runtime).to be >= normal_tracker.max_runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -162,7 +162,9 @@ RSpec.describe LooseForeignKeys::ProcessDeletedRecordsService, feature_category:
|
|||
end
|
||||
|
||||
before do
|
||||
stub_const('LooseForeignKeys::ModificationTracker::MAX_DELETES', 2)
|
||||
allow_next_instance_of(LooseForeignKeys::ModificationTracker) do |instance|
|
||||
allow(instance).to receive(:max_deletes).and_return(2)
|
||||
end
|
||||
stub_const('LooseForeignKeys::CleanerService::DELETE_LIMIT', 1)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -166,4 +166,64 @@ RSpec.describe LooseForeignKeys::CleanupWorker, feature_category: :cell do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'turbo mode' do
|
||||
context 'when turbo mode is off' do
|
||||
where(:database_name, :feature_flag) do
|
||||
:main | :loose_foreign_keys_turbo_mode_main
|
||||
:ci | :loose_foreign_keys_turbo_mode_ci
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
stub_feature_flags(feature_flag => false)
|
||||
end
|
||||
|
||||
it 'does not use TurboModificationTracker' do
|
||||
allow_next_instance_of(LooseForeignKeys::TurboModificationTracker) do |instance|
|
||||
expect(instance).not_to receive(:over_limit?)
|
||||
end
|
||||
|
||||
perform_for(db: database_name)
|
||||
end
|
||||
|
||||
it 'logs not using turbo mode' do
|
||||
expect_next_instance_of(LooseForeignKeys::CleanupWorker) do |instance|
|
||||
expect(instance).to receive(:log_extra_metadata_on_done).with(:stats, a_hash_including(turbo_mode: false))
|
||||
end
|
||||
|
||||
perform_for(db: database_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when turbo mode is on' do
|
||||
where(:database_name, :feature_flag) do
|
||||
:main | :loose_foreign_keys_turbo_mode_main
|
||||
:ci | :loose_foreign_keys_turbo_mode_ci
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
stub_feature_flags(feature_flag => true)
|
||||
end
|
||||
|
||||
it 'does not use TurboModificationTracker' do
|
||||
expect_next_instance_of(LooseForeignKeys::TurboModificationTracker) do |instance|
|
||||
expect(instance).to receive(:over_limit?).at_least(:once)
|
||||
end
|
||||
|
||||
perform_for(db: database_name)
|
||||
end
|
||||
|
||||
it 'logs using turbo mode' do
|
||||
expect_next_instance_of(LooseForeignKeys::CleanupWorker) do |instance|
|
||||
expect(instance).to receive(:log_extra_metadata_on_done).with(:stats, a_hash_including(turbo_mode: true))
|
||||
end
|
||||
|
||||
perform_for(db: database_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue