Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-06 18:10:06 +00:00
parent 0a5ea888dc
commit 52dbfea964
53 changed files with 511 additions and 179 deletions

View file

@ -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'

View file

@ -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 : '');
}

View file

@ -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>

View file

@ -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>

View file

@ -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 },
});
},
});
}

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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?

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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 } }

View 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

View file

@ -0,0 +1,5 @@
---
title: Stop exposing has_downloadable_artifacts in pipelines.json
merge_request: 60950
author:
type: performance

View file

@ -0,0 +1,5 @@
---
title: Fixed dollar signs in suggestions getting replaced incorrectly
merge_request: 61041
author:
type: fixed

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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).

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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`

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
\

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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!

View file

@ -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';

View file

@ -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

View file

@ -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

View file

@ -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(() => {

View file

@ -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 },
});
};

View file

@ -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',
},
};

View file

@ -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

View file

@ -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}"")

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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