Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0a5ea888dc
commit
52dbfea964
53 changed files with 511 additions and 179 deletions
|
@ -13,15 +13,10 @@
|
|||
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/322903
|
||||
Graphql/Descriptions:
|
||||
Exclude:
|
||||
- 'app/graphql/types/container_expiration_policy_cadence_enum.rb'
|
||||
- 'app/graphql/types/container_expiration_policy_keep_enum.rb'
|
||||
- 'app/graphql/types/container_expiration_policy_older_than_enum.rb'
|
||||
- 'app/graphql/types/packages/package_type_enum.rb'
|
||||
- 'app/graphql/types/snippets/blob_action_enum.rb'
|
||||
- 'app/graphql/types/snippets/type_enum.rb'
|
||||
- 'app/graphql/types/snippets/visibility_scopes_enum.rb'
|
||||
- 'ee/app/graphql/ee/types/list_limit_metric_enum.rb'
|
||||
- 'ee/app/graphql/types/alert_management/payload_alert_field_name_enum.rb'
|
||||
- 'ee/app/graphql/types/epic_state_enum.rb'
|
||||
- 'ee/app/graphql/types/health_status_enum.rb'
|
||||
- 'ee/app/graphql/types/iteration_state_enum.rb'
|
||||
|
|
|
@ -232,7 +232,7 @@ export function insertMarkdownText({
|
|||
.join('\n');
|
||||
}
|
||||
} else if (tag.indexOf(textPlaceholder) > -1) {
|
||||
textToInsert = tag.replace(textPlaceholder, selected.replace(/\\n/g, '\n'));
|
||||
textToInsert = tag.replace(textPlaceholder, () => selected.replace(/\\n/g, '\n'));
|
||||
} else {
|
||||
textToInsert = String(startChar) + tag + selected + (wrap ? tag : '');
|
||||
}
|
||||
|
|
|
@ -18,9 +18,13 @@ export default {
|
|||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
sections: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
maxValue: Object.keys(ACTION_LABELS).length,
|
||||
sections: Object.keys(ACTION_SECTIONS),
|
||||
actionSections: Object.keys(ACTION_SECTIONS),
|
||||
computed: {
|
||||
progressValue() {
|
||||
return Object.values(this.actions).filter((a) => a.completed).length;
|
||||
|
@ -38,6 +42,9 @@ export default {
|
|||
);
|
||||
return actions;
|
||||
},
|
||||
svgFor(section) {
|
||||
return this.sections[section].svg;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -59,8 +66,12 @@ export default {
|
|||
<gl-progress-bar :value="progressValue" :max="$options.maxValue" />
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-md-3 gl-mt-5">
|
||||
<div v-for="section in $options.sections" :key="section" class="col gl-mb-6">
|
||||
<learn-gitlab-section-card :section="section" :actions="actionsFor(section)" />
|
||||
<div v-for="section in $options.actionSections" :key="section" class="col gl-mb-6">
|
||||
<learn-gitlab-section-card
|
||||
:section="section"
|
||||
:svg="svgFor(section)"
|
||||
:actions="actionsFor(section)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { GlCard } from '@gitlab/ui';
|
||||
import { imagePath } from '~/lib/utils/common_utils';
|
||||
import { ACTION_LABELS, ACTION_SECTIONS } from '../constants';
|
||||
|
||||
import LearnGitlabSectionLink from './learn_gitlab_section_link.vue';
|
||||
|
@ -16,6 +15,10 @@ export default {
|
|||
required: true,
|
||||
type: String,
|
||||
},
|
||||
svg: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
actions: {
|
||||
required: true,
|
||||
type: Object,
|
||||
|
@ -28,17 +31,12 @@ export default {
|
|||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
svg(section) {
|
||||
return imagePath(`learn_gitlab/section_${section}.svg`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-card class="gl-pt-0 learn-gitlab-section-card">
|
||||
<div class="learn-gitlab-section-card-header">
|
||||
<img :src="svg(section)" />
|
||||
<img :src="svg" />
|
||||
<h2 class="gl-font-lg gl-mb-3">{{ $options.i18n[section].title }}</h2>
|
||||
<p class="gl-text-gray-700 gl-mb-6">{{ $options.i18n[section].description }}</p>
|
||||
</div>
|
||||
|
|
|
@ -12,6 +12,7 @@ function initLearnGitlab() {
|
|||
}
|
||||
|
||||
const actions = convertObjectPropsToCamelCase(JSON.parse(el.dataset.actions));
|
||||
const sections = convertObjectPropsToCamelCase(JSON.parse(el.dataset.sections));
|
||||
|
||||
const { learnGitlabA } = gon.experiments;
|
||||
|
||||
|
@ -20,7 +21,9 @@ function initLearnGitlab() {
|
|||
return new Vue({
|
||||
el,
|
||||
render(createElement) {
|
||||
return createElement(learnGitlabA ? LearnGitlabA : LearnGitlabB, { props: { actions } });
|
||||
return createElement(learnGitlabA ? LearnGitlabA : LearnGitlabB, {
|
||||
props: { actions, sections },
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,14 +36,6 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
displayPipelineActions() {
|
||||
return (
|
||||
this.pipeline.flags.retryable ||
|
||||
this.pipeline.flags.cancelable ||
|
||||
this.pipeline.details.manual_actions.length ||
|
||||
this.pipeline.details.has_downloadable_artifacts
|
||||
);
|
||||
},
|
||||
actions() {
|
||||
if (!this.pipeline || !this.pipeline.details) {
|
||||
return [];
|
||||
|
@ -54,9 +46,6 @@ export default {
|
|||
isCancelling() {
|
||||
return this.cancelingPipeline === this.pipeline.id;
|
||||
},
|
||||
showArtifacts() {
|
||||
return this.pipeline.details.has_downloadable_artifacts;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
pipeline() {
|
||||
|
@ -79,7 +68,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="displayPipelineActions" class="gl-text-right">
|
||||
<div class="gl-text-right">
|
||||
<div class="btn-group">
|
||||
<pipelines-manual-actions v-if="actions.length > 0" :actions="actions" />
|
||||
|
||||
|
@ -113,7 +102,7 @@ export default {
|
|||
@click="handleCancelClick"
|
||||
/>
|
||||
|
||||
<pipeline-multi-actions v-if="showArtifacts" :pipeline-id="pipeline.id" />
|
||||
<pipeline-multi-actions :pipeline-id="pipeline.id" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -11,7 +11,7 @@ module Types
|
|||
}.freeze
|
||||
|
||||
::ContainerExpirationPolicy.cadence_options.each do |option, description|
|
||||
value OPTIONS_MAPPING[option], description, value: option.to_s
|
||||
value OPTIONS_MAPPING[option], description: description, value: option.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ module Types
|
|||
}.freeze
|
||||
|
||||
::ContainerExpirationPolicy.keep_n_options.each do |option, description|
|
||||
value OPTIONS_MAPPING[option], description, value: option
|
||||
value OPTIONS_MAPPING[option], description: description, value: option
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module Types
|
|||
}.freeze
|
||||
|
||||
::ContainerExpirationPolicy.older_than_options.each do |option, description|
|
||||
value OPTIONS_MAPPING[option], description, value: option.to_s
|
||||
value OPTIONS_MAPPING[option], description: description, value: option.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module Types
|
|||
|
||||
::Packages::Package.package_types.keys.each do |package_type|
|
||||
type_name = PACKAGE_TYPE_NAMES.fetch(package_type.to_sym, package_type.capitalize)
|
||||
value package_type.to_s.upcase, "Packages from the #{type_name} package manager", value: package_type.to_s
|
||||
value package_type.to_s.upcase, description: "Packages from the #{type_name} package manager", value: package_type.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,6 +36,20 @@ module LearnGitlabHelper
|
|||
Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_b, subject: current_user)
|
||||
end
|
||||
|
||||
def onboarding_sections_data
|
||||
{
|
||||
workspace: {
|
||||
svg: image_path("learn_gitlab/section_workspace.svg")
|
||||
},
|
||||
plan: {
|
||||
svg: image_path("learn_gitlab/section_plan.svg")
|
||||
},
|
||||
deploy: {
|
||||
svg: image_path("learn_gitlab/section_deploy.svg")
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def action_urls
|
||||
|
|
|
@ -1076,14 +1076,6 @@ module Ci
|
|||
complete? && builds.latest.with_exposed_artifacts.exists?
|
||||
end
|
||||
|
||||
def has_downloadable_artifacts?
|
||||
if downloadable_artifacts.loaded?
|
||||
downloadable_artifacts.any?
|
||||
else
|
||||
downloadable_artifacts.exists?
|
||||
end
|
||||
end
|
||||
|
||||
def branch_updated?
|
||||
strong_memoize(:branch_updated) do
|
||||
push_details.branch_updated?
|
||||
|
|
|
@ -8,7 +8,6 @@ class PipelineDetailsEntity < Ci::PipelineEntity
|
|||
end
|
||||
|
||||
expose :details do
|
||||
expose :has_downloadable_artifacts?, as: :has_downloadable_artifacts
|
||||
expose :artifacts, unless: proc { options[:disable_artifacts] } do |pipeline, options|
|
||||
rel = pipeline.downloadable_artifacts
|
||||
|
||||
|
|
|
@ -3,8 +3,18 @@
|
|||
class IssueRebalancingService
|
||||
MAX_ISSUE_COUNT = 10_000
|
||||
BATCH_SIZE = 100
|
||||
SMALLEST_BATCH_SIZE = 5
|
||||
RETRIES_LIMIT = 3
|
||||
TooManyIssues = Class.new(StandardError)
|
||||
|
||||
TIMING_CONFIGURATION = [
|
||||
[0.1.seconds, 0.05.seconds], # short timings, lock_timeout: 100ms, sleep after LockWaitTimeout: 50ms
|
||||
[0.5.seconds, 0.05.seconds],
|
||||
[1.second, 0.5.seconds],
|
||||
[1.second, 0.5.seconds],
|
||||
[5.seconds, 1.second]
|
||||
].freeze
|
||||
|
||||
def initialize(issue)
|
||||
@issue = issue
|
||||
@base = Issue.relative_positioning_query_base(issue)
|
||||
|
@ -23,14 +33,23 @@ class IssueRebalancingService
|
|||
assign_positions(start, indexed_ids)
|
||||
.sort_by(&:first)
|
||||
.each_slice(BATCH_SIZE) do |pairs_with_position|
|
||||
update_positions(pairs_with_position, 'rebalance issue positions in batches ordered by id')
|
||||
if Feature.enabled?(:issue_rebalancing_with_retry)
|
||||
update_positions_with_retry(pairs_with_position, 'rebalance issue positions in batches ordered by id')
|
||||
else
|
||||
update_positions(pairs_with_position, 'rebalance issue positions in batches ordered by id')
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
Issue.transaction do
|
||||
indexed_ids.each_slice(BATCH_SIZE) do |pairs|
|
||||
pairs_with_position = assign_positions(start, pairs)
|
||||
update_positions(pairs_with_position, 'rebalance issue positions')
|
||||
|
||||
if Feature.enabled?(:issue_rebalancing_with_retry)
|
||||
update_positions_with_retry(pairs_with_position, 'rebalance issue positions')
|
||||
else
|
||||
update_positions(pairs_with_position, 'rebalance issue positions')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -52,12 +71,37 @@ class IssueRebalancingService
|
|||
end
|
||||
end
|
||||
|
||||
def update_positions_with_retry(pairs_with_position, query_name)
|
||||
retries = 0
|
||||
batch_size = pairs_with_position.size
|
||||
|
||||
until pairs_with_position.empty?
|
||||
begin
|
||||
update_positions(pairs_with_position.first(batch_size), query_name)
|
||||
pairs_with_position = pairs_with_position.drop(batch_size)
|
||||
retries = 0
|
||||
rescue ActiveRecord::StatementTimeout, ActiveRecord::QueryCanceled => ex
|
||||
raise ex if batch_size < SMALLEST_BATCH_SIZE
|
||||
|
||||
if (retries += 1) == RETRIES_LIMIT
|
||||
# shrink the batch size in half when RETRIES limit is reached and update still fails perhaps because batch size is still too big
|
||||
batch_size = (batch_size / 2).to_i
|
||||
retries = 0
|
||||
end
|
||||
|
||||
retry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_positions(pairs_with_position, query_name)
|
||||
values = pairs_with_position.map do |id, index|
|
||||
"(#{id}, #{index})"
|
||||
end.join(', ')
|
||||
|
||||
run_update_query(values, query_name)
|
||||
Gitlab::Database::WithLockRetries.new(timing_configuration: TIMING_CONFIGURATION, klass: self.class).run do
|
||||
run_update_query(values, query_name)
|
||||
end
|
||||
end
|
||||
|
||||
def run_update_query(values, query_name)
|
||||
|
|
|
@ -1,28 +1,3 @@
|
|||
- if project_nav_tab? :snippets
|
||||
= nav_link(controller: :snippets) do
|
||||
= link_to project_snippets_path(@project), class: 'shortcuts-snippets', data: { qa_selector: 'snippets_link' } do
|
||||
.nav-icon-container
|
||||
= sprite_icon('snippet')
|
||||
%span.nav-item-name
|
||||
= _('Snippets')
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: :snippets, html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_snippets_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Snippets')
|
||||
|
||||
= nav_link(controller: :project_members) do
|
||||
= link_to project_project_members_path(@project), title: _('Members'), class: 'qa-members-link', id: 'js-onboarding-members-link' do
|
||||
.nav-icon-container
|
||||
= sprite_icon('users')
|
||||
%span.nav-item-name
|
||||
= _('Members')
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(path: %w[members#show], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to project_project_members_path(@project) do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Members')
|
||||
|
||||
- if project_nav_tab? :settings
|
||||
= nav_link(path: sidebar_settings_paths) do
|
||||
= link_to edit_project_path(@project) do
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
- page_title _("Learn GitLab")
|
||||
- add_page_specific_style 'page_bundles/learn_gitlab'
|
||||
|
||||
#js-learn-gitlab-app{ data: { actions: onboarding_actions_data(@project).to_json } }
|
||||
#js-learn-gitlab-app{ data: { actions: onboarding_actions_data(@project).to_json, sections: onboarding_sections_data.to_json } }
|
||||
|
|
6
changelogs/unreleased/fix-smtp-pool-errors.yml
Normal file
6
changelogs/unreleased/fix-smtp-pool-errors.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Fix SMTP errors when delivering service desk thank you emails with SMTP pool
|
||||
enabled
|
||||
merge_request: 60843
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/mo-display-artifacts-dropdown.yml
Normal file
5
changelogs/unreleased/mo-display-artifacts-dropdown.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Stop exposing has_downloadable_artifacts in pipelines.json
|
||||
merge_request: 60950
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed dollar signs in suggestions getting replaced incorrectly
|
||||
merge_request: 61041
|
||||
author:
|
||||
type: fixed
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Increase load time of project select dropdowns
|
||||
title: Decrease load time of project select dropdowns
|
||||
merge_request: 61117
|
||||
author:
|
||||
type: performance
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: issue_rebalancing_with_retry
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::project management
|
||||
default_enabled: false
|
|
@ -27,7 +27,7 @@ have a high degree of confidence in being able to perform them accurately.
|
|||
|
||||
## Not all data is automatically replicated
|
||||
|
||||
If you are using any GitLab features that Geo [doesn't support](../index.md#limitations),
|
||||
If you are using any GitLab features that Geo [doesn't support](../replication/datatypes.md#limitations-on-replicationverification),
|
||||
you must make separate provisions to ensure that the **secondary** node has an
|
||||
up-to-date copy of any data associated with that feature. This may extend the
|
||||
required scheduled maintenance period significantly.
|
||||
|
@ -40,8 +40,7 @@ final transfer inside the maintenance window) will then transfer only the
|
|||
|
||||
Repository-centric strategies for using `rsync` effectively can be found in the
|
||||
[moving repositories](../../operations/moving_repositories.md) documentation; these strategies can
|
||||
be adapted for use with any other file-based data, such as GitLab Pages (to
|
||||
be found in `/var/opt/gitlab/gitlab-rails/shared/pages` if using Omnibus).
|
||||
be adapted for use with any other file-based data, such as [GitLab Pages](../../pages/index.md#change-storage-path).
|
||||
|
||||
## Preflight checks
|
||||
|
||||
|
|
|
@ -181,6 +181,25 @@ When something is marked to be updated in the tracking database instance, asynch
|
|||
|
||||
This new architecture allows GitLab to be resilient to connectivity issues between the nodes. It doesn't matter how long the **secondary** node is disconnected from the **primary** node as it will be able to replay all the events in the correct order and become synchronized with the **primary** node again.
|
||||
|
||||
## Limitations
|
||||
|
||||
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** node redirects (for HTTP) or proxies (for SSH) the request to the **primary** node instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/-/issues/1381), except when using Git over HTTP with credentials embedded within the URI. For example, `https://user:password@secondary.tld`.
|
||||
- The **primary** node has to be online for OAuth login to happen. Existing sessions and Git are not affected. Support for the **secondary** node 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. We are working on improving this experience. See [Omnibus GitLab issue #2978](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/2978) for details.
|
||||
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** node.
|
||||
- [Selective synchronization](replication/configuration.md#selective-synchronization) applies only to files and repositories. Other datasets are replicated to the **secondary** node in full, making it inappropriate for use as an access control mechanism.
|
||||
- Object pools for forked project deduplication work only on the **primary** node, and are duplicated on the **secondary** node.
|
||||
- GitLab Runners cannot register with a **secondary** node. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/-/issues/3294).
|
||||
- Geo **secondary** nodes can not be configured to [use high-availability configurations of PostgreSQL](https://gitlab.com/groups/gitlab-org/-/epics/2536).
|
||||
- [Selective synchronization](replication/configuration.md#selective-synchronization) only limits what repositories are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accomodate compliance / export control use cases.
|
||||
|
||||
### Limitations on replication/verification
|
||||
|
||||
There is a complete list of all GitLab [data types](replication/datatypes.md) and [existing support for replication and verification](replication/datatypes.md#limitations-on-replicationverification).
|
||||
|
||||
## Setup instructions
|
||||
|
||||
For setup instructions, see [Setting up Geo](setup/index.md).
|
||||
|
@ -275,25 +294,6 @@ For more information on removing a Geo node, see [Removing **secondary** Geo nod
|
|||
|
||||
To find out how to disable Geo, see [Disabling Geo](replication/disable_geo.md).
|
||||
|
||||
## Limitations
|
||||
|
||||
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** node redirects (for HTTP) or proxies (for SSH) the request to the **primary** node instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/-/issues/1381), except when using Git over HTTP with credentials embedded within the URI. For example, `https://user:password@secondary.tld`.
|
||||
- The **primary** node has to be online for OAuth login to happen. Existing sessions and Git are not affected. Support for the **secondary** node 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. We are working on improving this experience. See [Omnibus GitLab issue #2978](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/2978) for details.
|
||||
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** node.
|
||||
- [Selective synchronization](replication/configuration.md#selective-synchronization) applies only to files and repositories. Other datasets are replicated to the **secondary** node in full, making it inappropriate for use as an access control mechanism.
|
||||
- Object pools for forked project deduplication work only on the **primary** node, and are duplicated on the **secondary** node.
|
||||
- GitLab Runners cannot register with a **secondary** node. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/-/issues/3294).
|
||||
- Geo **secondary** nodes can not be configured to [use high-availability configurations of PostgreSQL](https://gitlab.com/groups/gitlab-org/-/epics/2536).
|
||||
- [Selective synchronization](replication/configuration.md#selective-synchronization) only limits what repositories are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accomodate compliance / export control use cases.
|
||||
|
||||
### Limitations on replication/verification
|
||||
|
||||
There is a complete list of all GitLab [data types](replication/datatypes.md) and [existing support for replication and verification](replication/datatypes.md#limitations-on-replicationverification).
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
For answers to common questions, see the [Geo FAQ](replication/faq.md).
|
||||
|
|
|
@ -853,6 +853,12 @@ To resolve this issue:
|
|||
the **primary** node using IPv4 in the `/etc/hosts` file. Alternatively, you should
|
||||
[enable IPv6 on the **primary** node](https://docs.gitlab.com/omnibus/settings/nginx.html#setting-the-nginx-listen-address-or-addresses).
|
||||
|
||||
### GitLab Pages return 404 errors after promoting
|
||||
|
||||
This is due to [Pages data not being managed by Geo](datatypes.md#limitations-on-replicationverification).
|
||||
Find advice to resolve those errors in the
|
||||
[Pages administration documentation](../../../administration/pages/index.md#404-error-after-promoting-a-geo-secondary-to-a-primary-node).
|
||||
|
||||
## Fixing client errors
|
||||
|
||||
### Authorization errors from LFS HTTP(s) client requests
|
||||
|
|
|
@ -1167,6 +1167,17 @@ date > /var/opt/gitlab/gitlab-rails/shared/pages/.update
|
|||
|
||||
If you've customized the Pages storage path, adjust the command above to use your custom path.
|
||||
|
||||
### 404 error after promoting a Geo secondary to a primary node
|
||||
|
||||
These are due to the Pages files not being among the
|
||||
[supported data types](../geo/replication/datatypes.md#limitations-on-replicationverification).
|
||||
|
||||
It is possible to copy the subfolders and files in the [Pages path](#change-storage-path)
|
||||
to the new primary node to resolve this.
|
||||
For example, you can adapt the `rsync` strategy from the
|
||||
[moving repositories documenation](../operations/moving_repositories.md).
|
||||
Alternatively, run the CI pipelines of those projects that contain a `pages` job again.
|
||||
|
||||
### Failed to connect to the internal GitLab API
|
||||
|
||||
If you have enabled [API-based configuration](#gitlab-api-based-configuration) and see the following error:
|
||||
|
|
|
@ -24,7 +24,9 @@ Fields that are deprecated are marked with **{warning-solid}**.
|
|||
Items (fields, enums, etc) that have been removed according to our [deprecation process](../index.md#deprecation-and-removal-process) can be found
|
||||
in [Removed Items](../removed_items.md).
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
<!-- vale off -->
|
||||
<!-- Docs linting disabled after this line. -->
|
||||
<!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-vale-tests -->
|
||||
|
||||
## `Query` type
|
||||
|
||||
|
|
|
@ -10,8 +10,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
|
||||
--->
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
|
||||
# Metrics Dictionary
|
||||
|
||||
This file is autogenerated, please do not edit directly.
|
||||
|
@ -30,6 +28,10 @@ The Metrics Dictionary is based on the following metrics definition YAML files:
|
|||
Each table includes a `milestone`, which corresponds to the GitLab version when the metric
|
||||
was released.
|
||||
|
||||
<!-- vale off -->
|
||||
<!-- Docs linting disabled after this line. -->
|
||||
<!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-vale-tests -->
|
||||
|
||||
## Metrics Definitions
|
||||
|
||||
### `active_user_count`
|
||||
|
|
|
@ -302,6 +302,9 @@ A 404 can also be related to incorrect permissions. If [Pages Access Control](pa
|
|||
navigates to the Pages URL and receives a 404 response, it is possible that the user does not have permission to view the site.
|
||||
To fix this, verify that the user is a member of the project.
|
||||
|
||||
For Geo instances, 404 errors on Pages occur after promoting a secondary to a primary.
|
||||
Find more details in the [Pages administration documentation](../../../administration/pages/index.md#404-error-after-promoting-a-geo-secondary-to-a-primary-node)
|
||||
|
||||
### Cannot play media content on Safari
|
||||
|
||||
Safari requires the web server to support the [Range request header](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/CreatingVideoforSafarioniPhone/CreatingVideoforSafarioniPhone.html#//apple_ref/doc/uid/TP40006514-SW6)
|
||||
|
|
|
@ -38,7 +38,7 @@ module Gitlab
|
|||
|
||||
if from_address
|
||||
add_email_participant
|
||||
send_thank_you_email!
|
||||
send_thank_you_email
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -92,8 +92,8 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def send_thank_you_email!
|
||||
Notify.service_desk_thank_you_email(@issue.id).deliver_later!
|
||||
def send_thank_you_email
|
||||
Notify.service_desk_thank_you_email(@issue.id).deliver_later
|
||||
end
|
||||
|
||||
def message_including_template
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
Items (fields, enums, etc) that have been removed according to our [deprecation process](../index.md#deprecation-and-removal-process) can be found
|
||||
in [Removed Items](../removed_items.md).
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
<!-- vale off -->
|
||||
<!-- Docs linting disabled after this line. -->
|
||||
<!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-vale-tests -->
|
||||
\
|
||||
|
||||
:plain
|
||||
|
|
|
@ -18,8 +18,6 @@ module Gitlab
|
|||
|
||||
Please do not edit this file directly, check generate_metrics_dictionary task on lib/tasks/gitlab/usage_data.rake.
|
||||
--->
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
Each table includes a `milestone`, which corresponds to the GitLab version when the metric
|
||||
was released.
|
||||
|
||||
<!-- vale off -->
|
||||
<!-- Docs linting disabled after this line. -->
|
||||
<!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-vale-tests -->
|
||||
|
||||
## Metrics Definitions
|
||||
|
||||
\
|
||||
|
|
41
lib/sidebars/projects/menus/members_menu.rb
Normal file
41
lib/sidebars/projects/menus/members_menu.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Projects
|
||||
module Menus
|
||||
class MembersMenu < ::Sidebars::Menu
|
||||
override :link
|
||||
def link
|
||||
project_project_members_path(context.project)
|
||||
end
|
||||
|
||||
override :extra_container_html_options
|
||||
def extra_container_html_options
|
||||
{
|
||||
id: 'js-onboarding-members-link'
|
||||
}
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Members')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'users'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
can?(context.current_user, :read_project_member, context.project)
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ controller: :project_members }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
41
lib/sidebars/projects/menus/snippets_menu.rb
Normal file
41
lib/sidebars/projects/menus/snippets_menu.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Projects
|
||||
module Menus
|
||||
class SnippetsMenu < ::Sidebars::Menu
|
||||
override :link
|
||||
def link
|
||||
project_snippets_path(context.project)
|
||||
end
|
||||
|
||||
override :extra_container_html_options
|
||||
def extra_container_html_options
|
||||
{
|
||||
class: 'shortcuts-snippets'
|
||||
}
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Snippets')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'snippet'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
can?(context.current_user, :read_snippet, context.project)
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ controller: :snippets }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,6 +22,8 @@ module Sidebars
|
|||
add_menu(Sidebars::Projects::Menus::AnalyticsMenu.new(context))
|
||||
add_menu(confluence_or_wiki_menu)
|
||||
add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::SnippetsMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::MembersMenu.new(context))
|
||||
end
|
||||
|
||||
override :render_raw_menus_partial
|
||||
|
|
|
@ -13,11 +13,6 @@ module QA
|
|||
include SubMenus::Settings
|
||||
include SubMenus::Packages
|
||||
|
||||
view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do
|
||||
element :snippets_link
|
||||
element :members_link
|
||||
end
|
||||
|
||||
def click_merge_requests
|
||||
within_sidebar do
|
||||
click_element(:sidebar_menu_link, menu_item: 'Merge requests')
|
||||
|
@ -38,13 +33,13 @@ module QA
|
|||
|
||||
def click_snippets
|
||||
within_sidebar do
|
||||
click_element(:snippets_link)
|
||||
click_element(:sidebar_menu_link, menu_item: 'Snippets')
|
||||
end
|
||||
end
|
||||
|
||||
def click_members
|
||||
within_sidebar do
|
||||
click_element(:members_link)
|
||||
click_element(:sidebar_menu_link, menu_item: 'Members')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -88,12 +88,18 @@ RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do
|
|||
def build_filter_text(pipeline, initial_text)
|
||||
filter_source = {}
|
||||
input_text = initial_text
|
||||
result = nil
|
||||
|
||||
pipeline.filters.each do |filter_klass|
|
||||
filter_source[filter_klass] = input_text
|
||||
# store inputs for current filter_klass
|
||||
filter_source[filter_klass] = { input_text: input_text, input_result: result }
|
||||
|
||||
output = filter_klass.call(input_text, context)
|
||||
filter = filter_klass.new(input_text, context, result)
|
||||
output = filter.call
|
||||
|
||||
# save these for the next filter_klass
|
||||
input_text = output
|
||||
result = filter.result
|
||||
end
|
||||
|
||||
filter_source
|
||||
|
@ -111,7 +117,12 @@ RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do
|
|||
pipeline.filters.each do |filter_klass|
|
||||
label = filter_klass.name.demodulize.delete_suffix('Filter').truncate(20)
|
||||
|
||||
x.report(label) { filter_klass.call(filter_source[filter_klass], context) }
|
||||
x.report(label) do
|
||||
filter = filter_klass.new(filter_source[filter_klass][:input_text],
|
||||
context,
|
||||
filter_source[filter_klass][:input_result])
|
||||
filter.call
|
||||
end
|
||||
end
|
||||
|
||||
x.compare!
|
||||
|
|
|
@ -51,6 +51,25 @@ describe('init markdown', () => {
|
|||
expect(textArea.value).toEqual(`${initialValue}- `);
|
||||
});
|
||||
|
||||
it('inserts dollar signs correctly', () => {
|
||||
const initialValue = '';
|
||||
|
||||
textArea.value = initialValue;
|
||||
textArea.selectionStart = 0;
|
||||
textArea.selectionEnd = 0;
|
||||
|
||||
insertMarkdownText({
|
||||
textArea,
|
||||
text: textArea.value,
|
||||
tag: '```suggestion:-0+0\n{text}\n```',
|
||||
blockTag: true,
|
||||
selected: '# Does not parse the `$` currently.',
|
||||
wrap: false,
|
||||
});
|
||||
|
||||
expect(textArea.value).toContain('# Does not parse the `$` currently.');
|
||||
});
|
||||
|
||||
it('inserts the tag on a new line if the current one is not empty', () => {
|
||||
const initialValue = 'some text';
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
|
|||
class="learn-gitlab-section-card-header"
|
||||
>
|
||||
<img
|
||||
src="/assets/learn_gitlab/section_workspace.svg"
|
||||
src="workspace.svg"
|
||||
/>
|
||||
|
||||
<h2
|
||||
|
@ -246,7 +246,7 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
|
|||
class="learn-gitlab-section-card-header"
|
||||
>
|
||||
<img
|
||||
src="/assets/learn_gitlab/section_plan.svg"
|
||||
src="plan.svg"
|
||||
/>
|
||||
|
||||
<h2
|
||||
|
@ -324,7 +324,7 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
|
|||
class="learn-gitlab-section-card-header"
|
||||
>
|
||||
<img
|
||||
src="/assets/learn_gitlab/section_deploy.svg"
|
||||
src="deploy.svg"
|
||||
/>
|
||||
|
||||
<h2
|
||||
|
|
|
@ -11,7 +11,7 @@ exports[`Learn GitLab Section Card renders correctly 1`] = `
|
|||
class="learn-gitlab-section-card-header"
|
||||
>
|
||||
<img
|
||||
src="/assets/learn_gitlab/section_workspace.svg"
|
||||
src="workspace.svg"
|
||||
/>
|
||||
|
||||
<h2
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { GlProgressBar } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue';
|
||||
import { testActions } from './mock_data';
|
||||
import { testActions, testSections } from './mock_data';
|
||||
|
||||
describe('Learn GitLab Design A', () => {
|
||||
let wrapper;
|
||||
|
||||
const createWrapper = () => {
|
||||
wrapper = mount(LearnGitlabA, { propsData: { actions: testActions } });
|
||||
wrapper = mount(LearnGitlabA, { propsData: { actions: testActions, sections: testSections } });
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -3,6 +3,7 @@ import LearnGitlabSectionCard from '~/pages/projects/learn_gitlab/components/lea
|
|||
import { testActions } from './mock_data';
|
||||
|
||||
const defaultSection = 'workspace';
|
||||
const testImage = 'workspace.svg';
|
||||
|
||||
describe('Learn GitLab Section Card', () => {
|
||||
let wrapper;
|
||||
|
@ -14,7 +15,7 @@ describe('Learn GitLab Section Card', () => {
|
|||
|
||||
const createWrapper = () => {
|
||||
wrapper = shallowMount(LearnGitlabSectionCard, {
|
||||
propsData: { section: defaultSection, actions: testActions },
|
||||
propsData: { section: defaultSection, actions: testActions, svg: testImage },
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -45,3 +45,15 @@ export const testActions = {
|
|||
svg: 'http://example.com/images/illustration.svg',
|
||||
},
|
||||
};
|
||||
|
||||
export const testSections = {
|
||||
workspace: {
|
||||
svg: 'workspace.svg',
|
||||
},
|
||||
deploy: {
|
||||
svg: 'deploy.svg',
|
||||
},
|
||||
plan: {
|
||||
svg: 'plan.svg',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -96,6 +96,17 @@ RSpec.describe LearnGitlabHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.onboarding_sections_data' do
|
||||
subject(:sections) { helper.onboarding_sections_data }
|
||||
|
||||
it 'has the right keys' do
|
||||
expect(sections.keys).to contain_exactly(:deploy, :plan, :workspace)
|
||||
end
|
||||
it 'has the svg' do
|
||||
expect(sections.values.map { |section| section.keys }).to eq([[:svg]] * 3)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.learn_gitlab_experiment_tracking_category' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
|
|
@ -90,11 +90,6 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
|
|||
context 'when quick actions are present' do
|
||||
let(:label) { create(:label, project: project, title: 'label1') }
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
let!(:user) { create(:user, username: 'user1') }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'applies quick action commands present on templates' do
|
||||
file_content = %(Text from template \n/label ~#{label.title} \n/milestone %"#{milestone.name}"")
|
||||
|
|
27
spec/lib/sidebars/projects/menus/members_menu_spec.rb
Normal file
27
spec/lib/sidebars/projects/menus/members_menu_spec.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Projects::Menus::MembersMenu do
|
||||
let(:project) { build(:project) }
|
||||
let(:user) { project.owner }
|
||||
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
|
||||
|
||||
subject { described_class.new(context) }
|
||||
|
||||
describe '#render?' do
|
||||
context 'when user cannot access members' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.render?).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can access members' do
|
||||
it 'returns true' do
|
||||
expect(subject.render?).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
spec/lib/sidebars/projects/menus/snippets_menu_spec.rb
Normal file
27
spec/lib/sidebars/projects/menus/snippets_menu_spec.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Projects::Menus::SnippetsMenu do
|
||||
let(:project) { build(:project) }
|
||||
let(:user) { project.owner }
|
||||
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
|
||||
|
||||
subject { described_class.new(context) }
|
||||
|
||||
describe '#render?' do
|
||||
context 'when user cannot access snippets' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.render?).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can access snippets' do
|
||||
it 'returns true' do
|
||||
expect(subject.render?).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4491,18 +4491,4 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
.not_to exceed_query_limit(control_count)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_downloadable_artifacts?' do
|
||||
it 'returns false when when pipeline does not have downloadable artifacts' do
|
||||
pipeline = create(:ci_pipeline, :success)
|
||||
|
||||
expect(pipeline.has_downloadable_artifacts?). to eq(false)
|
||||
end
|
||||
|
||||
it 'returns false when when pipeline does not have downloadable artifacts' do
|
||||
pipeline = create(:ci_pipeline, :with_codequality_reports)
|
||||
|
||||
expect(pipeline.has_downloadable_artifacts?). to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ RSpec.describe PipelineDetailsEntity do
|
|||
expect(subject[:details])
|
||||
.to include :duration, :finished_at
|
||||
expect(subject[:details])
|
||||
.to include :stages, :artifacts, :has_downloadable_artifacts, :manual_actions, :scheduled_actions
|
||||
.to include :stages, :artifacts, :manual_actions, :scheduled_actions
|
||||
expect(subject[:details][:status]).to include :icon, :favicon, :text, :label
|
||||
end
|
||||
|
||||
|
@ -186,35 +186,5 @@ RSpec.describe PipelineDetailsEntity do
|
|||
end
|
||||
|
||||
it_behaves_like 'public artifacts'
|
||||
|
||||
context 'when pipeline has downloadable artifacts' do
|
||||
subject(:entity) { described_class.represent(pipeline, request: request, disable_artifacts: disable_artifacts).as_json }
|
||||
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, :with_codequality_reports) }
|
||||
|
||||
context 'when disable_artifacts is true' do
|
||||
subject(:entity) { described_class.represent(pipeline, request: request, disable_artifacts: true).as_json }
|
||||
|
||||
it 'excludes artifacts data' do
|
||||
expect(entity[:details]).not_to include(:artifacts)
|
||||
end
|
||||
|
||||
it 'returns true for has_downloadable_artifacts' do
|
||||
expect(entity[:details][:has_downloadable_artifacts]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when disable_artifacts is false' do
|
||||
subject(:entity) { described_class.represent(pipeline, request: request, disable_artifacts: false).as_json }
|
||||
|
||||
it 'includes artifacts data' do
|
||||
expect(entity[:details]).to include(:artifacts)
|
||||
end
|
||||
|
||||
it 'returns true for has_downloadable_artifacts' do
|
||||
expect(entity[:details][:has_downloadable_artifacts]).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,31 +3,35 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe IssueRebalancingService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:project, reload: true) { create(:project) }
|
||||
let_it_be(:user) { project.creator }
|
||||
let_it_be(:start) { RelativePositioning::START_POSITION }
|
||||
let_it_be(:max_pos) { RelativePositioning::MAX_POSITION }
|
||||
let_it_be(:min_pos) { RelativePositioning::MIN_POSITION }
|
||||
let_it_be(:clump_size) { 300 }
|
||||
|
||||
let_it_be(:unclumped) do
|
||||
(0..clump_size).to_a.map do |i|
|
||||
let_it_be(:unclumped, reload: true) do
|
||||
(1..clump_size).to_a.map do |i|
|
||||
create(:issue, project: project, author: user, relative_position: start + (1024 * i))
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:end_clump) do
|
||||
(0..clump_size).to_a.map do |i|
|
||||
let_it_be(:end_clump, reload: true) do
|
||||
(1..clump_size).to_a.map do |i|
|
||||
create(:issue, project: project, author: user, relative_position: max_pos - i)
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:start_clump) do
|
||||
(0..clump_size).to_a.map do |i|
|
||||
let_it_be(:start_clump, reload: true) do
|
||||
(1..clump_size).to_a.map do |i|
|
||||
create(:issue, project: project, author: user, relative_position: min_pos + i)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(issue_rebalancing_with_retry: false)
|
||||
end
|
||||
|
||||
def issues_in_position_order
|
||||
project.reload.issues.reorder(relative_position: :asc).to_a
|
||||
end
|
||||
|
@ -101,19 +105,70 @@ RSpec.describe IssueRebalancingService do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'rebalancing is retried on statement timeout exceptions' do
|
||||
subject { described_class.new(project.issues.first) }
|
||||
|
||||
it 'retries update statement' do
|
||||
call_count = 0
|
||||
allow(subject).to receive(:run_update_query) do
|
||||
call_count += 1
|
||||
if call_count < 13
|
||||
raise(ActiveRecord::QueryCanceled)
|
||||
else
|
||||
call_count = 0 if call_count == 13 + 16 # 16 = 17 sub-batches - 1 call that succeeded as part of 5th batch
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# call math:
|
||||
# batches start at 100 and are split in half after every 3 retries if ActiveRecord::StatementTimeout exception is raised.
|
||||
# We raise ActiveRecord::StatementTimeout exception for 13 calls:
|
||||
# 1. 100 => 3 calls
|
||||
# 2. 100/2=50 => 3 calls + 3 above = 6 calls, raise ActiveRecord::StatementTimeout
|
||||
# 3. 50/2=25 => 3 calls + 6 above = 9 calls, raise ActiveRecord::StatementTimeout
|
||||
# 4. 25/2=12 => 3 calls + 9 above = 12 calls, raise ActiveRecord::StatementTimeout
|
||||
# 5. 12/2=6 => 1 call + 12 above = 13 calls, run successfully
|
||||
#
|
||||
# so out of 100 elements we created batches of 6 items => 100/6 = 17 sub-batches of 6 or less elements
|
||||
#
|
||||
# project.issues.count: 900 issues, so 9 batches of 100 => 9 * (13+16) = 261
|
||||
expect(subject).to receive(:update_positions).exactly(261).times.and_call_original
|
||||
|
||||
subject.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue_rebalancing_optimization feature flag is on' do
|
||||
before do
|
||||
stub_feature_flags(issue_rebalancing_optimization: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'IssueRebalancingService shared examples'
|
||||
|
||||
context 'when issue_rebalancing_with_retry feature flag is on' do
|
||||
before do
|
||||
stub_feature_flags(issue_rebalancing_with_retry: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'IssueRebalancingService shared examples'
|
||||
it_behaves_like 'rebalancing is retried on statement timeout exceptions'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue_rebalancing_optimization feature flag is on' do
|
||||
context 'when issue_rebalancing_optimization feature flag is off' do
|
||||
before do
|
||||
stub_feature_flags(issue_rebalancing_optimization: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'IssueRebalancingService shared examples'
|
||||
|
||||
context 'when issue_rebalancing_with_retry feature flag is on' do
|
||||
before do
|
||||
stub_feature_flags(issue_rebalancing_with_retry: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'IssueRebalancingService shared examples'
|
||||
it_behaves_like 'rebalancing is retried on statement timeout exceptions'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -863,6 +863,46 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Snippets' do
|
||||
before do
|
||||
render
|
||||
end
|
||||
|
||||
context 'when user can access snippets' do
|
||||
it 'shows Snippets link' do
|
||||
expect(rendered).to have_link('Snippets', href: project_snippets_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user cannot access snippets' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not show Snippets link' do
|
||||
expect(rendered).not_to have_link('Snippets')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Members' do
|
||||
before do
|
||||
render
|
||||
end
|
||||
|
||||
context 'when user can access members' do
|
||||
it 'show Members link' do
|
||||
expect(rendered).to have_link('Members', href: project_project_members_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user cannot access members' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'show Members link' do
|
||||
expect(rendered).not_to have_link('Members')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'operations settings tab' do
|
||||
describe 'archive projects' do
|
||||
before do
|
||||
|
|
|
@ -30,5 +30,11 @@ module Mail
|
|||
def deliver!(mail)
|
||||
@pool.with { |conn| conn.deliver!(mail) }
|
||||
end
|
||||
|
||||
# This makes it compatible with Mail's `#deliver!` method
|
||||
# https://github.com/mikel/mail/blob/22a7afc23f253319965bf9228a0a430eec94e06d/lib/mail/message.rb#L271
|
||||
def settings
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,5 +64,27 @@ describe Mail::SMTPPool do
|
|||
|
||||
expect(MockSMTP.deliveries.size).to eq(1)
|
||||
end
|
||||
|
||||
context 'when called from Mail:Message' do
|
||||
before do
|
||||
mail.delivery_method(described_class, { pool: described_class.create_pool })
|
||||
end
|
||||
|
||||
describe '#deliver' do
|
||||
it 'delivers mail' do
|
||||
mail.deliver
|
||||
|
||||
expect(MockSMTP.deliveries.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deliver!' do
|
||||
it 'delivers mail' do
|
||||
mail.deliver!
|
||||
|
||||
expect(MockSMTP.deliveries.size).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue