diff options
54 files changed, 467 insertions, 427 deletions
@@ -290,7 +290,7 @@ gem 'autoprefixer-rails', '10.2.5.1' gem 'terser', '1.0.2' gem 'addressable', '~> 2.8' -gem 'tanuki_emoji', '~> 0.5' +gem 'tanuki_emoji', '~> 0.6' gem 'gon', '~> 6.4.0' gem 'request_store', '~> 1.5' gem 'base32', '~> 0.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index a946c2cb792..a66a2fff1bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1261,7 +1261,7 @@ GEM sys-filesystem (1.4.3) ffi (~> 1.1) sysexits (1.2.0) - tanuki_emoji (0.5.0) + tanuki_emoji (0.6.0) temple (0.8.2) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) @@ -1649,7 +1649,7 @@ DEPENDENCIES stackprof (~> 0.2.15) state_machines-activerecord (~> 0.8.0) sys-filesystem (~> 1.4.3) - tanuki_emoji (~> 0.5) + tanuki_emoji (~> 0.6) terser (= 1.0.2) test-prof (~> 1.0.7) test_file_finder (~> 0.1.3) diff --git a/app/assets/javascripts/blob/components/blob_header_default_actions.vue b/app/assets/javascripts/blob/components/blob_header_default_actions.vue index 12bcb24b0cc..61baf4fa495 100644 --- a/app/assets/javascripts/blob/components/blob_header_default_actions.vue +++ b/app/assets/javascripts/blob/components/blob_header_default_actions.vue @@ -1,6 +1,7 @@ <script> import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; import { sprintf, s__ } from '~/locale'; +import { setUrlParams, relativePathToAbsolute, getBaseURL } from '~/lib/utils/url_utility'; import { BTN_COPY_CONTENTS_TITLE, BTN_DOWNLOAD_TITLE, @@ -56,7 +57,7 @@ export default { }, computed: { downloadUrl() { - return `${this.rawPath}?inline=false`; + return setUrlParams({ inline: false }, relativePathToAbsolute(this.rawPath, getBaseURL())); }, copyDisabled() { return this.activeViewer === RICH_BLOB_VIEWER; diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js index b003302ec8e..7c424088c8b 100644 --- a/app/assets/javascripts/persistent_user_callout.js +++ b/app/assets/javascripts/persistent_user_callout.js @@ -13,23 +13,25 @@ export default class PersistentUserCallout { this.featureId = featureId; this.groupId = groupId; this.deferLinks = parseBoolean(deferLinks); + this.closeButtons = this.container.querySelectorAll('.js-close'); this.init(); } init() { - const closeButton = this.container.querySelector('.js-close'); const followLink = this.container.querySelector('.js-follow-link'); - if (closeButton) { - this.handleCloseButtonCallout(closeButton); + if (this.closeButtons.length) { + this.handleCloseButtonCallout(); } else if (followLink) { this.handleFollowLinkCallout(followLink); } } - handleCloseButtonCallout(closeButton) { - closeButton.addEventListener('click', (event) => this.dismiss(event)); + handleCloseButtonCallout() { + this.closeButtons.forEach((closeButton) => { + closeButton.addEventListener('click', this.dismiss); + }); if (this.deferLinks) { this.container.addEventListener('click', (event) => { @@ -47,7 +49,7 @@ export default class PersistentUserCallout { followLink.addEventListener('click', (event) => this.registerCalloutWithLink(event)); } - dismiss(event, deferredLinkOptions = null) { + dismiss = (event, deferredLinkOptions = null) => { event.preventDefault(); axios @@ -57,6 +59,9 @@ export default class PersistentUserCallout { }) .then(() => { this.container.remove(); + this.closeButtons.forEach((closeButton) => { + closeButton.removeEventListener('click', this.dismiss); + }); if (deferredLinkOptions) { const { href, target } = deferredLinkOptions; @@ -70,7 +75,7 @@ export default class PersistentUserCallout { ), }); }); - } + }; registerCalloutWithLink(event) { event.preventDefault(); diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue index cd6f7427fd6..bc433db3eb8 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue @@ -2,20 +2,12 @@ import { GlEmptyState, GlButton } from '@gitlab/ui'; import { startCodeQualityWalkthrough, track } from '~/code_quality_walkthrough/utils'; import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue'; -import ExperimentTracking from '~/experimentation/experiment_tracking'; import { getExperimentData } from '~/experimentation/utils'; -import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; import PipelinesCiTemplates from './pipelines_ci_templates.vue'; export default { i18n: { - title: s__('Pipelines|Build with confidence'), - description: s__(`Pipelines|GitLab CI/CD can automatically build, - test, and deploy your code. Let GitLab take care of time - consuming tasks, so you can spend more time creating.`), - aboutRunnersBtnText: s__('Pipelines|Learn about Runners'), - installRunnersBtnText: s__('Pipelines|Install GitLab Runners'), codeQualityTitle: s__('Pipelines|Improve code quality with GitLab CI/CD'), codeQualityDescription: s__(`Pipelines|To keep your codebase simple, readable, and accessible to contributors, use GitLab CI/CD @@ -56,15 +48,9 @@ export default { }, }, computed: { - ciHelpPagePath() { - return helpPagePath('ci/quick_start/index.md'); - }, isCodeQualityExperimentActive() { return this.canSetCi && Boolean(getExperimentData('code_quality_walkthrough')); }, - isCiRunnerTemplatesExperimentActive() { - return this.canSetCi && Boolean(getExperimentData('ci_runner_templates')); - }, }, mounted() { startCodeQualityWalkthrough(); @@ -73,10 +59,6 @@ export default { trackClick() { track('cta_clicked'); }, - trackCiRunnerTemplatesClick(action) { - const tracking = new ExperimentTracking('ci_runner_templates'); - tracking.event(action); - }, }, }; </script> @@ -98,33 +80,6 @@ export default { </gl-empty-state> </template> </gitlab-experiment> - <gitlab-experiment v-else-if="isCiRunnerTemplatesExperimentActive" name="ci_runner_templates"> - <template #control><pipelines-ci-templates /></template> - <template #candidate> - <gl-empty-state - :title="$options.i18n.title" - :svg-path="emptyStateSvgPath" - :description="$options.i18n.description" - > - <template #actions> - <gl-button - :href="ciRunnerSettingsPath" - variant="confirm" - @click="trackCiRunnerTemplatesClick('install_runners_button_clicked')" - > - {{ $options.i18n.installRunnersBtnText }} - </gl-button> - <gl-button - :href="ciHelpPagePath" - variant="default" - @click="trackCiRunnerTemplatesClick('learn_button_clicked')" - > - {{ $options.i18n.aboutRunnersBtnText }} - </gl-button> - </template> - </gl-empty-state> - </template> - </gitlab-experiment> <pipelines-ci-templates v-else-if="canSetCi" :ci-runner-settings-path="ciRunnerSettingsPath" diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js index 46361c6eb32..e10ad8bafaa 100644 --- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js +++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js @@ -1,7 +1,5 @@ import { s__, sprintf } from '~/locale'; -export const EXPERIMENT_NAME = 'ci_runner_templates'; - export const README_URL = 'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md'; diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue index 57cc25caa25..811176821e2 100644 --- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue +++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue @@ -1,16 +1,10 @@ <script> import { GlModal, GlSprintf, GlLink } from '@gitlab/ui'; import awsCloudFormationImageUrl from 'images/aws-cloud-formation.png'; -import ExperimentTracking from '~/experimentation/experiment_tracking'; +import Tracking from '~/tracking'; import { getBaseURL, objectToQuery } from '~/lib/utils/url_utility'; import { __, s__ } from '~/locale'; -import { - EXPERIMENT_NAME, - README_URL, - CF_BASE_URL, - TEMPLATES_BASE_URL, - EASY_BUTTONS, -} from './constants'; +import { README_URL, CF_BASE_URL, TEMPLATES_BASE_URL, EASY_BUTTONS } from './constants'; export default { components: { @@ -18,6 +12,7 @@ export default { GlSprintf, GlLink, }, + mixins: [Tracking.mixin()], props: { modalId: { type: String, @@ -39,8 +34,9 @@ export default { return CF_BASE_URL + objectToQuery(params); }, trackCiRunnerTemplatesClick(stackName) { - const tracking = new ExperimentTracking(EXPERIMENT_NAME); - tracking.event(`template_clicked_${stackName}`); + this.track('template_clicked', { + label: stackName, + }); }, }, i18n: { diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index 78170fab7a7..d8da448a323 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -44,6 +44,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont :task_num, :title, :discussion_locked, + :issue_iid, label_ids: [], assignee_ids: [], reviewer_ids: [], diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index b6090fa2548..310b8d1d477 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -52,7 +52,6 @@ class Projects::PipelinesController < Projects::ApplicationController respond_to do |format| format.html do enable_code_quality_walkthrough_experiment - enable_ci_runner_templates_experiment enable_runners_availability_section_experiment end format.json do @@ -320,19 +319,6 @@ class Projects::PipelinesController < Projects::ApplicationController end end - def enable_ci_runner_templates_experiment - experiment(:ci_runner_templates, namespace: project.root_ancestor) do |e| - e.exclude! unless current_user - e.exclude! unless can?(current_user, :create_pipeline, project) - e.exclude! if @pipelines_count.to_i > 0 - e.exclude! if helpers.has_gitlab_ci?(project) - - e.control {} - e.candidate {} - e.publish_to_database - end - end - def enable_runners_availability_section_experiment return unless current_user return unless can?(current_user, :create_pipeline, project) diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb index 32b0d7b3fe3..87c8bf5cb28 100644 --- a/app/helpers/users/callouts_helper.rb +++ b/app/helpers/users/callouts_helper.rb @@ -10,6 +10,7 @@ module Users REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout' UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout' SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout' + REGISTRATION_ENABLED_CALLOUT_ALLOWED_CONTROLLER_PATHS = [/^root/, /^dashboard\S*/, /^admin\S*/].freeze def show_gke_cluster_integration_callout?(project) active_nav_link?(controller: sidebar_operations_paths) && @@ -47,7 +48,8 @@ module Users !Gitlab.com? && current_user&.admin? && signup_enabled? && - !user_dismissed?(REGISTRATION_ENABLED_CALLOUT) + !user_dismissed?(REGISTRATION_ENABLED_CALLOUT) && + REGISTRATION_ENABLED_CALLOUT_ALLOWED_CONTROLLER_PATHS.any? { |path| controller.controller_path.match?(path) } end def dismiss_two_factor_auth_recovery_settings_check diff --git a/app/views/admin/application_settings/_initial_branch_name.html.haml b/app/views/admin/application_settings/_default_branch.html.haml index 8832bc02056..f5f45d7a6e9 100644 --- a/app/views/admin/application_settings/_initial_branch_name.html.haml +++ b/app/views/admin/application_settings/_default_branch.html.haml @@ -1,13 +1,17 @@ -= form_for @application_setting, url: repository_admin_application_settings_path(anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f| += gitlab_ui_form_for @application_setting, url: repository_admin_application_settings_path(anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) - fallback_branch_name = "<code>#{Gitlab::DefaultBranch.value}</code>" %fieldset .form-group - = f.label :default_branch_name, _('Default initial branch name'), class: 'label-light' + = f.label :default_branch_name, _('Initial default branch name'), class: 'label-light' = f.text_field :default_branch_name, placeholder: Gitlab::DefaultBranch.value, class: 'form-control gl-form-input' %span.form-text.text-muted = (s_("AdminSettings|If not specified at the group or instance level, the default is %{default_initial_branch_name}. Does not affect existing repositories.") % { default_initial_branch_name: fallback_branch_name } ).html_safe + = render 'shared/default_branch_protection', f: f + + = render_if_exists 'admin/application_settings/group_owners_can_manage_default_branch_protection_setting', form: f + = f.submit _('Save changes'), class: 'gl-button btn-confirm' diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml index e56c898b236..b0810d3d48a 100644 --- a/app/views/admin/application_settings/_visibility_and_access.html.haml +++ b/app/views/admin/application_settings/_visibility_and_access.html.haml @@ -2,9 +2,6 @@ = form_errors(@application_setting) %fieldset - = render 'shared/default_branch_protection', f: f - = render_if_exists 'admin/application_settings/group_owners_can_manage_default_branch_protection_setting', form: f - = render 'shared/project_creation_levels', f: f, method: :default_project_creation, legend: s_('ProjectCreationLevel|Default project creation protection') = render_if_exists 'admin/application_settings/default_project_deletion_protection_setting', form: f = render_if_exists 'admin/application_settings/default_delayed_project_deletion_setting', form: f diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml index ac200002cd2..c3a39ddf86d 100644 --- a/app/views/admin/application_settings/repository.html.haml +++ b/app/views/admin/application_settings/repository.html.haml @@ -5,13 +5,13 @@ %section.settings.as-default-branch-name.no-animate#js-default-branch-name{ class: ('expanded' if expanded_by_default?) } .settings-header %h4 - = _('Default initial branch name') + = _('Default branch') %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded_by_default? ? _('Collapse') : _('Expand') %p - = s_('AdminSettings|The default name for the initial branch of new repositories created in the instance.') + = s_('AdminSettings|Set the initial name and protections for the default branch of new repositories created in the instance.') .settings-content - = render 'initial_branch_name' + = render 'default_branch' %section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index d4b74665398..85dd218942f 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -35,7 +35,6 @@ = render_if_exists 'groups/settings/ip_restriction', f: f, group: @group = render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group = render 'groups/settings/lfs', f: f - = render 'groups/settings/default_branch_protection', f: f, group: @group = render 'groups/settings/project_creation_level', f: f, group: @group = render 'groups/settings/subgroup_creation_level', f: f, group: @group = render_if_exists 'groups/settings/prevent_forking', f: f, group: @group diff --git a/app/views/groups/settings/repository/_initial_branch_name.html.haml b/app/views/groups/settings/repository/_default_branch.html.haml index 15a3bacf12d..f2644465a49 100644 --- a/app/views/groups/settings/repository/_initial_branch_name.html.haml +++ b/app/views/groups/settings/repository/_default_branch.html.haml @@ -1,22 +1,24 @@ %section.settings.as-default-branch-name.no-animate#js-default-branch-name{ class: ('expanded' if expanded_by_default?) } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = _('Default initial branch name') + = _('Default branch') %button.gl-button.js-settings-toggle{ type: 'button' } = expanded_by_default? ? _('Collapse') : _('Expand') %p - = s_('GroupSettings|The default name for the initial branch of new repositories created in the group.') + = s_('GroupSettings|Set the initial name and protections for the default branch of new repositories created in the group.') .settings-content - = form_for @group, url: group_path(@group, anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f| + = gitlab_ui_form_for @group, url: group_path(@group, anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f| = form_errors(@group) - fallback_branch_name = "<code>#{Gitlab::DefaultBranch.value(object: @group)}</code>" %fieldset .form-group - = f.label :default_branch_name, _('Default initial branch name'), class: 'label-light' + = f.label :default_branch_name, _('Initial default branch name'), class: 'label-light' = f.text_field :default_branch_name, value: group.namespace_settings&.default_branch_name, placeholder: Gitlab::DefaultBranch.value(object: @group), class: 'form-control' %span.form-text.text-muted = (s_("GroupSettings|If not specified at the group or instance level, the default is %{default_initial_branch_name}. Does not affect existing repositories.") % { default_initial_branch_name: fallback_branch_name }).html_safe - = f.hidden_field :redirect_target, value: "repository_settings" - = f.submit _('Save changes'), class: 'btn gl-button btn-confirm' + = render 'groups/settings/default_branch_protection', f: f, group: @group + + = f.hidden_field :redirect_target, value: "repository_settings" + = f.submit _('Save changes'), class: 'btn gl-button btn-confirm' diff --git a/app/views/groups/settings/repository/show.html.haml b/app/views/groups/settings/repository/show.html.haml index a5819320405..072c8c4d821 100644 --- a/app/views/groups/settings/repository/show.html.haml +++ b/app/views/groups/settings/repository/show.html.haml @@ -4,4 +4,4 @@ - deploy_token_description = s_('DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group.') = render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description -= render "initial_branch_name", group: @group += render "default_branch", group: @group diff --git a/app/views/layouts/header/_registration_enabled_callout.html.haml b/app/views/layouts/header/_registration_enabled_callout.html.haml index 90f3ac61614..d1d23c86c81 100644 --- a/app/views/layouts/header/_registration_enabled_callout.html.haml +++ b/app/views/layouts/header/_registration_enabled_callout.html.haml @@ -1,14 +1,17 @@ - return unless show_registration_enabled_user_callout? = render 'shared/global_alert', - title: _('Open registration is enabled on your instance.'), + title: _('Anyone can register for an account.'), variant: :warning, alert_class: 'js-registration-enabled-callout', alert_data: { feature_id: Users::CalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: callouts_path }, close_button_data: { testid: 'close-registration-enabled-callout' } do .gl-alert-body - = html_escape(_('%{anchorOpen}Learn more%{anchorClose} about how you can customize / disable registration on your instance.')) % { anchorOpen: "<a href=\"#{help_page_path('user/admin_area/settings/sign_up_restrictions')}\" class=\"gl-link\">".html_safe, anchorClose: '</a>'.html_safe } + = _('Only allow anyone to register for accounts on GitLab instances that you intend to be used by anyone. Allowing anyone to register makes GitLab instances more vulnerable.') .gl-alert-actions = link_to general_admin_application_settings_path(anchor: 'js-signup-settings'), class: 'btn gl-alert-action btn-confirm btn-md gl-button' do %span.gl-button-text - = _('View setting') + = _('Turn off') + %button.btn.gl-alert-action.btn-default.btn-md.gl-button.js-close + %span.gl-button-text + = _('Acknowledge') diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 8d6c0e29b6a..f6ed6c26752 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -6,7 +6,7 @@ - create_mr_text = can_create_confidential_merge_request? ? _('Create confidential merge request') : _('Create merge request') - can_create_path = can_create_branch_project_issue_path(@project, @issue) - - create_mr_path = project_new_merge_request_path(@project, merge_request: { source_branch: @issue.to_branch_name, target_branch: @project.default_branch }) + - create_mr_path = project_new_merge_request_path(@project, merge_request: { source_branch: @issue.to_branch_name, target_branch: @project.default_branch, issue_iid: @issue.iid }) - create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid, format: :json) - refs_path = refs_namespace_project_path(@project.namespace, @project, search: '') diff --git a/app/views/shared/_default_branch_protection.html.haml b/app/views/shared/_default_branch_protection.html.haml index 7a6152f6d96..1a660f3f896 100644 --- a/app/views/shared/_default_branch_protection.html.haml +++ b/app/views/shared/_default_branch_protection.html.haml @@ -1,4 +1,4 @@ -%fieldset.form-group - %legend.h5.gl-border-none.gl-mt-0.gl-mb-3= _('Default branch protection') +.form-group + %legend.h5.gl-border-none.gl-mt-0.gl-mb-3= _('Initial default branch protection') - Gitlab::Access.protection_options.each do |option| = f.gitlab_ui_radio_component :default_branch_protection, option[:value], option[:label], help_text: option[:help_text] diff --git a/config/feature_flags/experiment/ci_runner_templates.yml b/config/feature_flags/experiment/ci_runner_templates.yml deleted file mode 100644 index e791581f67a..00000000000 --- a/config/feature_flags/experiment/ci_runner_templates.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_runner_templates -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58357 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326725 -milestone: "14.0" -type: experiment -group: group::activation -default_enabled: false diff --git a/doc/administration/geo/index.md b/doc/administration/geo/index.md index 51308367056..c0a5b2e0ff0 100644 --- a/doc/administration/geo/index.md +++ b/doc/administration/geo/index.md @@ -210,6 +210,18 @@ This list of limitations only reflects the latest version of GitLab. If you are 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). +### View replication data on the primary site + +If you try to view replication data on the primary site, you receive a warning that this may be inconsistent: + +> Viewing projects and designs data from a primary site is not possible when using a unified URL. Visit the secondary site directly. + +The only way to view projects replication data for a particular secondary site is to visit that secondary site directly. For example, `https://<IP of your secondary site>/admin/geo/replication/projects`. +An [epic exists](https://gitlab.com/groups/gitlab-org/-/epics/4623) to fix this limitation. + +The only way to view designs replication data for a particular secondary site is to visit that secondary site directly. For example, `https://<IP of your secondary site>/admin/geo/replication/designs`. +An [epic exists](https://gitlab.com/groups/gitlab-org/-/epics/4624) to fix this limitation. + ## Setup instructions For setup instructions, see [Setting up Geo](setup/index.md). diff --git a/doc/administration/geo/secondary_proxy/index.md b/doc/administration/geo/secondary_proxy/index.md index c8dcbb81312..768adab9101 100644 --- a/doc/administration/geo/secondary_proxy/index.md +++ b/doc/administration/geo/secondary_proxy/index.md @@ -140,6 +140,8 @@ for details. To use TLS certificates with Let's Encrypt, you can manually point the domain to one of the Geo sites, generate the certificate, then copy it to all other sites. +- [Viewing projects and designs data from a primary site is not possible when using a unified URL](../index.md#view-replication-data-on-the-primary-site). + ## Behavior of secondary sites when the primary Geo site is down Considering that web traffic is proxied to the primary, the behavior of the secondary sites differs when the primary diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md index a152c443902..d2cca1a5856 100644 --- a/doc/api/geo_nodes.md +++ b/doc/api/geo_nodes.md @@ -63,7 +63,8 @@ Example response: "sync_object_storage": false, "clone_protocol": "http", "web_edit_url": "https://primary.example.com/admin/geo/sites/3/edit", - "web_geo_projects_url": "http://secondary.example.com/admin/geo/projects", + "web_geo_projects_url": "https://secondary.example.com/admin/geo/projects", + "web_geo_replication_details_url": "https://secondary.example.com/admin/geo/sites/3/replication/lfs_objects", "_links": { "self": "https://primary.example.com/api/v4/geo_nodes/3", "status": "https://primary.example.com/api/v4/geo_nodes/3/status", @@ -72,6 +73,10 @@ Example response: } ``` +WARNING: +The `web_geo_projects_url` attribute is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80106) +for use in GitLab 14.9. + ## Retrieve configuration about all Geo nodes ```plaintext @@ -130,6 +135,7 @@ Example response: "clone_protocol": "http", "web_edit_url": "https://primary.example.com/admin/geo/sites/2/edit", "web_geo_projects_url": "https://secondary.example.com/admin/geo/projects", + "web_geo_replication_details_url": "https://secondary.example.com/admin/geo/sites/2/replication/lfs_objects", "_links": { "self":"https://primary.example.com/api/v4/geo_nodes/2", "status":"https://primary.example.com/api/v4/geo_nodes/2/status", @@ -139,6 +145,10 @@ Example response: ] ``` +WARNING: +The `web_geo_projects_url` attribute is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80106) +for use in GitLab 14.9. + ## Retrieve configuration about a specific Geo node ```plaintext @@ -228,6 +238,7 @@ Example response: "clone_protocol": "http", "web_edit_url": "https://primary.example.com/admin/geo/sites/2/edit", "web_geo_projects_url": "https://secondary.example.com/admin/geo/projects", + "web_geo_replication_details_url": "https://secondary.example.com/admin/geo/sites/2/replication/lfs_objects", "_links": { "self":"https://primary.example.com/api/v4/geo_nodes/2", "status":"https://primary.example.com/api/v4/geo_nodes/2/status", @@ -236,6 +247,10 @@ Example response: } ``` +WARNING: +The `web_geo_projects_url` attribute is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80106) +for use in GitLab 14.9. + ## Delete a Geo node Removes the Geo node. diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 5c8e2d5fc55..b98de46a72a 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -219,14 +219,14 @@ To update the Emoji aliases file (used for Emoji autocomplete), run the following: ```shell -bundle exec rake gemojione:aliases +bundle exec rake tanuki_emoji:aliases ``` To update the Emoji digests file (used for Emoji autocomplete), run the following: ```shell -bundle exec rake gemojione:digests +bundle exec rake tanuki_emoji:digests ``` This updates the file `fixtures/emojis/digests.json` based on the currently @@ -235,7 +235,7 @@ available Emoji. To generate a sprite file containing all the Emoji, run: ```shell -bundle exec rake gemojione:sprite +bundle exec rake tanuki_emoji:sprite ``` If new emoji are added, the sprite sheet may change size. To compensate for diff --git a/doc/development/service_ping/implement.md b/doc/development/service_ping/implement.md index 54f3f2ca618..25e841e113b 100644 --- a/doc/development/service_ping/implement.md +++ b/doc/development/service_ping/implement.md @@ -16,7 +16,7 @@ Service Ping consists of two kinds of data: To implement a new metric in Service Ping, follow these steps: 1. [Implement the required counter](#types-of-counters) -1. [Name and place the metric](#name-and-place-the-metric) +1. [Name and place the metric](metrics_dictionary.md#metric-key_path) 1. [Test counters manually using your Rails console](#test-counters-manually-using-your-rails-console) 1. [Generate the SQL query](#generate-the-sql-query) 1. [Optimize queries with `#database-lab`](#optimize-queries-with-database-lab) @@ -660,29 +660,6 @@ We return fallback values in these cases: | Timeouts, general failures | -1 | | Standard errors in counters | -2 | -## Name and place the metric - -Add the metric in one of the top-level keys: - -- `settings`: for settings related metrics. -- `counts_weekly`: for counters that have data for the most recent 7 days. -- `counts_monthly`: for counters that have data for the most recent 28 days. -- `counts`: for counters that have data for all time. - -### How to get a metric name suggestion - -The metric YAML generator can suggest a metric name for you. -To generate a metric name suggestion, first instrument the metric at the provided `key_path`. -Then, generate the metric's YAML definition and -return to the instrumentation and update it. - -1. Add the metric instrumentation to `lib/gitlab/usage_data.rb` inside one - of the [top-level keys](#name-and-place-the-metric), using any name you choose. -1. Run the [metrics YAML generator](metrics_dictionary.md#metrics-definition-and-validation). -1. Use the metric name suggestion to select a suitable metric name. -1. Update the instrumentation you created in the first step and change the metric name to the suggested name. -1. Update the metric's YAML definition with the correct `key_path`. - ## Test counters manually using your Rails console ```ruby diff --git a/doc/development/service_ping/metrics_dictionary.md b/doc/development/service_ping/metrics_dictionary.md index b3eb86ad434..588453be002 100644 --- a/doc/development/service_ping/metrics_dictionary.md +++ b/doc/development/service_ping/metrics_dictionary.md @@ -54,6 +54,51 @@ Each metric is defined in a separate YAML file consisting of a number of fields: | `options` | no | `object`: options information needed to calculate the metric value. | | `skip_validation` | no | This should **not** be set. [Used for imported metrics until we review, update and make them valid](https://gitlab.com/groups/gitlab-org/-/epics/5425). | +### Metric key_path + +The `key_path` of the metric is the location in the JSON Service Ping payload. + +The `key_path` could be composed from multiple parts separated by `.` and it must be unique. + +We recommend to add the metric in one of the top-level keys: + +- `settings`: for settings related metrics. +- `counts_weekly`: for counters that have data for the most recent 7 days. +- `counts_monthly`: for counters that have data for the most recent 28 days. +- `counts`: for counters that have data for all time. + +NOTE: +We can't control what the metric's `key_path` is, because some of them are generated dynamically in `usage_data.rb`. +For example, see [Redis HLL metrics](implement.md#redis-hll-counters). + +### Metric name + +To improve metric discoverability by a wider audience, each metric with +instrumentation added at an appointed `key_path` receives a `name` attribute +filled with the name suggestion, corresponding to the metric `data_source` and instrumentation. +Metric name suggestions can contain two types of elements: + +1. **User input prompts**: enclosed by angle brackets (`< >`), these pieces should be replaced or + removed when you create a metrics YAML file. +1. **Fixed suggestion**: plaintext parts generated according to well-defined algorithms. + They are based on underlying instrumentation, and must not be changed. + +For a metric name to be valid, it must not include any prompt, and fixed suggestions +must not be changed. + +#### Generate a metric name suggestion + +The metric YAML generator can suggest a metric name for you. +To generate a metric name suggestion, first instrument the metric at the provided `key_path`. +Then, generate the metric's YAML definition and +return to the instrumentation and update it. + +1. Add the metric instrumentation class to `lib/gitlab/usage/metrics/instrumentations/`. +1. Add the metric logic in the instrumentation class. +1. Run the [metrics YAML generator](metrics_dictionary.md#metrics-definition-and-validation). +1. Use the metric name suggestion to select a suitable metric name. +1. Update the metric's YAML definition with the correct `key_path`. + ### Metric statuses Metric definitions can have one of the following statuses: @@ -81,21 +126,6 @@ which has a related schema in `/config/metrics/objects_schemas/topology_schema.j - `all`: The metric data applies for the whole time the metric has been active (all-time interval). For example, the following metric counts all users that create issues: `/config/metrics/counts_all/20210216181115_issues.yml`. - `none`: The metric collects a type of data that's not tracked over time, such as settings and configuration information. Therefore, a time interval is not applicable. For example, `uuid` has no time interval applicable: `config/metrics/license/20210201124933_uuid.yml`. -### Metric name - -To improve metric discoverability by a wider audience, each metric with -instrumentation added at an appointed `key_path` receives a `name` attribute -filled with the name suggestion, corresponding to the metric `data_source` and instrumentation. -Metric name suggestions can contain two types of elements: - -1. **User input prompts**: Enclosed by `<>`, these pieces should be replaced or - removed when you create a metrics YAML file. -1. **Fixed suggestion**: Plaintext parts generated according to well-defined algorithms. - They are based on underlying instrumentation, and should not be changed. - -For a metric name to be valid, it must not include any prompt, and no fixed suggestions -should be changed. - ### Data category We use the following categories to classify a metric: diff --git a/doc/development/service_ping/review_guidelines.md b/doc/development/service_ping/review_guidelines.md index 137e11608cf..c7e602725d3 100644 --- a/doc/development/service_ping/review_guidelines.md +++ b/doc/development/service_ping/review_guidelines.md @@ -51,9 +51,9 @@ are regular backend changes. #### The Product Intelligence **reviewer** should - Perform a first-pass review on the merge request and suggest improvements to the author. -- Check the [metrics location](implement.md#name-and-place-the-metric) in +- Check the [metrics location](metrics_dictionary.md#metric-key_path) in the Service Ping JSON payload. -- Suggest that the author checks the [naming suggestion](implement.md#how-to-get-a-metric-name-suggestion) while +- Suggest that the author checks the [naming suggestion](metrics_dictionary.md#generate-a-metric-name-suggestion) while generating the metric's YAML definition. - Add the `~database` label and ask for a [database review](../database_review.md) for metrics that are based on Database. diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md index a581fd4aebc..da8bc4062f1 100644 --- a/doc/user/admin_area/settings/index.md +++ b/doc/user/admin_area/settings/index.md @@ -170,6 +170,8 @@ The **Repository** settings contain: - [Repository's custom initial branch name](../../project/repository/branches/default.md#instance-level-custom-initial-branch-name) - Set a custom branch name for new repositories created in your instance. +- [Repository's initial default branch protection](../../project/repository/branches/default.md#instance-level-default-branch-protection) - + Configure the branch protections to apply to every repository's default branch. - [Repository mirror](visibility_and_access_controls.md#enable-project-mirroring) - Configure repository mirroring. - [Repository storage](../../../administration/repository_storage_types.md) - Configure storage path settings. diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md index dd477a69874..2165dc54899 100644 --- a/doc/user/admin_area/settings/visibility_and_access_controls.md +++ b/doc/user/admin_area/settings/visibility_and_access_controls.md @@ -17,54 +17,6 @@ To access the visibility and access control options: 1. On the left sidebar, select **Settings > General**. 1. Expand the **Visibility and access controls** section. -## Protect default branches - -With this option, you can define [branch protections](../../project/protected_branches.md) -to apply to every repository's [default branch](../../project/repository/branches/default.md). -These protections specify the user roles with permission to push to default branches. - -This setting applies only to each repository's default branch. To protect other branches, -you must configure [branch protection in the repository](../../project/protected_branches.md), -or configure [branch protection for groups](../../group/index.md#change-the-default-branch-protection-of-a-group). - -To change the default branch protection for the entire instance: - -1. Sign in to GitLab as a user with Administrator access level. -1. On the top bar, select **Menu > Admin**. -1. On the left sidebar, select **Settings > General**. -1. Expand the **Visibility and access controls** section. -1. Select a **Default branch protection**: - - **Not protected** - Both developers and maintainers can push new commits - and force push. - - **Protected against pushes** - Developers cannot push new commits, but are - allowed to accept merge requests to the branch. Maintainers can push to the branch. - - **Partially protected** - Both developers and maintainers can push new commits, - but cannot force push. - - **Fully protected** - Developers cannot push new commits, but maintainers can. - No one can force push. -1. To allow group owners to override the instance's default branch protection, select - [**Allow owners to manage default branch protection per group**](#prevent-overrides-of-default-branch-protection). -1. Select **Save changes**. - -### Prevent overrides of default branch protection **(PREMIUM SELF)** - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211944) in GitLab 13.0. - -Instance-level protections for [default branch](../../project/repository/branches/default.md) -can be overridden on a per-group basis by the group's owner. In -[GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can -disable this privilege for group owners, enforcing the instance-level protection rule: - -1. Sign in to GitLab as a user with Administrator access level. -1. On the top bar, select **Menu > Admin**. -1. On the left sidebar, select **Settings > General**. -1. Expand the **Visibility and access controls** section. -1. Clear the **Allow owners to manage default branch protection per group** checkbox. -1. Select **Save changes**. - -NOTE: -GitLab administrators can still update the default branch protection of a group. - ## Define which roles can create projects Instance-level protections for project creation define which roles can diff --git a/doc/user/group/index.md b/doc/user/group/index.md index c2a42fcd072..bc28b3094e6 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -204,24 +204,17 @@ A to-do item is created for all the group and subgroup members. ## Change the default branch protection of a group -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7583) in GitLab 12.9. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7583) in GitLab 12.9. +> - [Settings moved and renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/340403) in GitLab 14.9. By default, every group inherits the branch protection set at the global level. -To change this setting for a specific group: - -1. On the top bar, select **Menu > Groups**. -1. Select **Your Groups**. -1. Find the group and select it. -1. From the left menu, select **Settings > General**. -1. Expand the **Permissions and group features** section. -1. Select the desired option in the **Default branch protection** dropdown list. -1. Select **Save changes**. +To change this setting for a specific group, see [group level default branch protection](../project/repository/branches/default.md#group-level-default-branch-protection). -To change this setting globally, see [Default branch protection](../admin_area/settings/visibility_and_access_controls.md#protect-default-branches). +To change this setting globally, see [initial default branch protection](../project/repository/branches/default.md#instance-level-default-branch-protection). NOTE: -In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can choose to [disable group owners from updating the default branch protection](../admin_area/settings/visibility_and_access_controls.md#prevent-overrides-of-default-branch-protection). +In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can choose to [disable group owners from updating the default branch protection](../project/repository/branches/default.md#prevent-overrides-of-default-branch-protection). ## Add projects to a group diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index ca35568bb56..292530e6c9c 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -29,7 +29,7 @@ When a branch is protected, the default behavior enforces these restrictions on ### Set the default branch protection level Administrators can set a default branch protection level in the -[Admin Area](../admin_area/settings/visibility_and_access_controls.md#protect-default-branches). +[Admin Area](../project/repository/branches/default.md#instance-level-default-branch-protection). ## Configure a protected branch diff --git a/doc/user/project/repository/branches/default.md b/doc/user/project/repository/branches/default.md index f56e42a6061..f9fd1a48b9a 100644 --- a/doc/user/project/repository/branches/default.md +++ b/doc/user/project/repository/branches/default.md @@ -81,13 +81,83 @@ overrides it. Users with at least the Owner role of groups and subgroups can configure the default branch name for a group: 1. Go to the group **Settings > Repository**. -1. Expand **Default initial branch name**. +1. Expand **Default branch**. 1. Change the default initial branch to a custom name of your choice. 1. Select **Save changes**. Projects created in this group after you change the setting use the custom branch name, unless a subgroup configuration overrides it. +## Protect initial default branches **(FREE SELF)** + +GitLab administrators and group owners can define [branch protections](../../../project/protected_branches.md) +to apply to every repository's [default branch](#default-branch) +at the [instance level](#instance-level-default-branch-protection) and +[group level](#group-level-default-branch-protection) with one of the following options: + +- **Not protected** - Both developers and maintainers can push new commits + and force push. +- **Protected against pushes** - Developers cannot push new commits, but are + allowed to accept merge requests to the branch. Maintainers can push to the branch. +- **Partially protected** - Both developers and maintainers can push new commits, + but cannot force push. +- **Fully protected** - Developers cannot push new commits, but maintainers can. + No one can force push. + +### Instance-level default branch protection **(FREE SELF)** + +This setting applies only to each repository's default branch. To protect other branches, +you must either: + +- Configure [branch protection in the repository](../../../project/protected_branches.md). +- Configure [branch protection for groups](../../../group/index.md#change-the-default-branch-protection-of-a-group). + +Administrators of self-managed instances can customize the initial default branch protection for projects hosted on that instance. Individual +groups and subgroups can override this instance-wide setting for their projects. + +1. On the top bar, select **Menu > Admin**. +1. On the left sidebar, select **Settings > Repository**. +1. Expand **Default branch**. +1. Select [**Initial default branch protection**](#protect-initial-default-branches). +1. To allow group owners to override the instance's default branch protection, select + [**Allow owners to manage default branch protection per group**](#prevent-overrides-of-default-branch-protection). +1. Select **Save changes**. + +#### Prevent overrides of default branch protection **(PREMIUM SELF)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211944) in GitLab 13.0. + +Instance-level protections for default branches +can be overridden on a per-group basis by the group's owner. In +[GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can +disable this privilege for group owners, enforcing the instance-level protection rule: + +1. On the top bar, select **Menu > Admin**. +1. On the left sidebar, select **Settings > Repository**. +1. Expand the **Default branch** section. +1. Clear the **Allow owners to manage default branch protection per group** checkbox. +1. Select **Save changes**. + +NOTE: +GitLab administrators can still update the default branch protection of a group. + +### Group-level default branch protection **(PREMIUM)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7583) in GitLab 12.9. +> - [Settings moved and renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/340403) in GitLab 14.9. + +Instance-level protections for [default branch](#default-branch) +can be overridden on a per-group basis by the group's owner. In +[GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can +[enforce protection of initial default branches](#prevent-overrides-of-default-branch-protection) +which locks this setting for group owners. + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Settings > Repository**. +1. Expand **Default branch**. +1. Select [**Initial default branch protection**](#protect-initial-default-branches). +1. Select **Save changes**. + ## Update the default branch name in your repository WARNING: diff --git a/fixtures/emojis/aliases.json b/fixtures/emojis/aliases.json index c054e5e9f43..3d2894c1e00 100644 --- a/fixtures/emojis/aliases.json +++ b/fixtures/emojis/aliases.json @@ -1,6 +1,4 @@ { - ":) ":"smile", - ":( ":"disappointed", "small_airplane":"airplane_small", "right_anger_bubble":"anger_right", "keycap_asterisk":"asterisk", @@ -54,6 +52,7 @@ "passenger_ship":"cruise_ship", "dagger_knife":"dagger", "desktop_computer":"desktop", + ":( ":"disappointed", "card_index_dividers":"dividers", "dove_of_peace":"dove", "drool":"drooling_face", @@ -488,6 +487,7 @@ "skull_and_crossbones":"skull_crossbones", "slightly_frowning_face":"slight_frown", "slightly_smiling_face":"slight_smile", + ":) ":"smile", "sneeze":"sneezing_face", "speaking_head_in_silhouette":"speaking_head", "left_speech_bubble":"speech_left", diff --git a/lib/tasks/tanuki_emoji.rake b/lib/tasks/tanuki_emoji.rake index 98d3920c07f..0dc7dd4e701 100644 --- a/lib/tasks/tanuki_emoji.rake +++ b/lib/tasks/tanuki_emoji.rake @@ -3,12 +3,20 @@ namespace :tanuki_emoji do desc 'Generates Emoji aliases fixtures' task aliases: :environment do + ALLOWED_ALIASES = [':)', ':('].freeze aliases = {} TanukiEmoji.index.all.each do |emoji| emoji.aliases.each do |emoji_alias| aliases[TanukiEmoji::Character.format_name(emoji_alias)] = emoji.name end + + emoji.ascii_aliases.intersection(ALLOWED_ALIASES).each do |ascii_alias| + # We add an extra space at the end so that when a user types ":) " + # we'd still match this alias and not show "cocos (keeling) islands" as the first result. + # The initial ":" is ignored when matching because it's our emoji prefix in Markdown. + aliases[ascii_alias + ' '] = emoji.name + end end aliases_json_file = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json') diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 70d6fde6883..134bcfc240d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -462,9 +462,6 @@ msgstr "" msgid "%{address} is an invalid IP address range" msgstr "" -msgid "%{anchorOpen}Learn more%{anchorClose} about how you can customize / disable registration on your instance." -msgstr "" - msgid "%{author_link} cloned %{original_issue} to %{new_issue}." msgstr "" @@ -1986,6 +1983,9 @@ msgstr "" msgid "AccountValidation|you may %{unsubscribe_link} at any time." msgstr "" +msgid "Acknowledge" +msgstr "" + msgid "Action" msgstr "" @@ -2589,6 +2589,9 @@ msgstr "" msgid "AdminSettings|A Let's Encrypt account will be configured for this GitLab instance using this email address. You will receive emails to warn of expiring certificates. %{link_start}Learn more.%{link_end}" msgstr "" +msgid "AdminSettings|Affects all new and existing groups." +msgstr "" + msgid "AdminSettings|All new projects can use the instance's shared runners by default." msgstr "" @@ -2664,6 +2667,9 @@ msgstr "" msgid "AdminSettings|Set a CI/CD template as the required pipeline configuration for all projects in the instance. Project CI/CD configuration merges into the required pipeline configuration when the pipeline runs. %{link_start}What is a required pipeline configuration?%{link_end}" msgstr "" +msgid "AdminSettings|Set the initial name and protections for the default branch of new repositories created in the instance." +msgstr "" + msgid "AdminSettings|Set the maximum size of GitLab Pages per project (0 for unlimited). %{link_start}Learn more.%{link_end}" msgstr "" @@ -2673,9 +2679,6 @@ msgstr "" msgid "AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects." msgstr "" -msgid "AdminSettings|The default name for the initial branch of new repositories created in the instance." -msgstr "" - msgid "AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire." msgstr "" @@ -3591,7 +3594,7 @@ msgstr "" msgid "Allow only the selected protocols to be used for Git access." msgstr "" -msgid "Allow owners to manage default branch protection per group" +msgid "Allow owners to manage default branch protection per group." msgstr "" msgid "Allow owners to manually add users outside of LDAP" @@ -4208,6 +4211,9 @@ msgstr "" msgid "Any namespace" msgstr "" +msgid "Anyone can register for an account." +msgstr "" + msgid "App ID" msgstr "" @@ -11561,9 +11567,6 @@ msgstr "" msgid "Default branch and protected branches" msgstr "" -msgid "Default branch protection" -msgstr "" - msgid "Default delayed project deletion" msgstr "" @@ -11582,9 +11585,6 @@ msgstr "" msgid "Default first day of the week in calendars and date pickers." msgstr "" -msgid "Default initial branch name" -msgstr "" - msgid "Default project deletion protection" msgstr "" @@ -17659,13 +17659,13 @@ msgstr "" msgid "GroupSettings|Select the project that contains your custom Insights file." msgstr "" -msgid "GroupSettings|Set the maximum size of GitLab Pages for this group. %{link_start}Learn more.%{link_end}" +msgid "GroupSettings|Set the initial name and protections for the default branch of new repositories created in the group." msgstr "" -msgid "GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found." +msgid "GroupSettings|Set the maximum size of GitLab Pages for this group. %{link_start}Learn more.%{link_end}" msgstr "" -msgid "GroupSettings|The default name for the initial branch of new repositories created in the group." +msgid "GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found." msgstr "" msgid "GroupSettings|The projects in this subgroup can be selected as templates for new projects created in the group. %{link_start}Learn more.%{link_end}" @@ -19601,6 +19601,12 @@ msgstr "" msgid "Inherited:" msgstr "" +msgid "Initial default branch name" +msgstr "" + +msgid "Initial default branch protection" +msgstr "" + msgid "Inline" msgstr "" @@ -25632,6 +25638,9 @@ msgstr "" msgid "Only admins can delete project" msgstr "" +msgid "Only allow anyone to register for accounts on GitLab instances that you intend to be used by anyone. Allowing anyone to register makes GitLab instances more vulnerable." +msgstr "" + msgid "Only effective when remote storage is enabled. Set to 0 for no size limit." msgstr "" @@ -25704,9 +25713,6 @@ msgstr "" msgid "Open raw" msgstr "" -msgid "Open registration is enabled on your instance." -msgstr "" - msgid "Open sidebar" msgstr "" @@ -27008,18 +27014,12 @@ msgstr "" msgid "Pipelines|Install GitLab Runner" msgstr "" -msgid "Pipelines|Install GitLab Runners" -msgstr "" - msgid "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource." msgstr "" msgid "Pipelines|Last Used" msgstr "" -msgid "Pipelines|Learn about Runners" -msgstr "" - msgid "Pipelines|Learn the basics of pipelines and .yml files" msgstr "" @@ -40609,9 +40609,6 @@ msgstr "" msgid "View seat usage" msgstr "" -msgid "View setting" -msgstr "" - msgid "View supported languages and frameworks" msgstr "" @@ -40636,6 +40633,9 @@ msgstr "" msgid "Viewing commit" msgstr "" +msgid "Viewing projects and designs data from a primary site is not possible when using a unified URL. Visit the secondary site directly. %{geo_help_url}" +msgstr "" + msgid "Violation" msgstr "" diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 15783fd990d..283d7b49674 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -296,10 +296,6 @@ RSpec.describe Projects::PipelinesController do it_behaves_like 'tracks assignment and records the subject', :code_quality_walkthrough, :namespace end - context 'ci_runner_templates experiment' do - it_behaves_like 'tracks assignment and records the subject', :ci_runner_templates, :namespace - end - context 'runners_availability_section experiment' do it_behaves_like 'tracks assignment and records the subject', :runners_availability_section, :namespace end diff --git a/spec/features/callouts/registration_enabled_spec.rb b/spec/features/callouts/registration_enabled_spec.rb index 4055965273f..79e99712183 100644 --- a/spec/features/callouts/registration_enabled_spec.rb +++ b/spec/features/callouts/registration_enabled_spec.rb @@ -5,6 +5,8 @@ require 'spec_helper' RSpec.describe 'Registration enabled callout' do let_it_be(:admin) { create(:admin) } let_it_be(:non_admin) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:callout_title) { _('Anyone can register for an account.') } context 'when "Sign-up enabled" setting is `true`' do before do @@ -14,23 +16,42 @@ RSpec.describe 'Registration enabled callout' do context 'when an admin is logged in' do before do sign_in(admin) + end + + it 'displays callout on admin and dashboard pages and root page' do + visit root_path + + expect(page).to have_content callout_title + expect(page).to have_link _('Turn off'), href: general_admin_application_settings_path(anchor: 'js-signup-settings') + visit root_dashboard_path + + expect(page).to have_content callout_title + + visit admin_root_path + + expect(page).to have_content callout_title end - it 'displays callout' do - expect(page).to have_content 'Open registration is enabled on your instance.' - expect(page).to have_link 'View setting', href: general_admin_application_settings_path(anchor: 'js-signup-settings') + it 'does not display callout on pages other than root, admin, or dashboard' do + visit project_issues_path(project) + + expect(page).not_to have_content callout_title end context 'when callout is dismissed', :js do before do + visit admin_root_path + find('[data-testid="close-registration-enabled-callout"]').click + wait_for_requests + visit root_dashboard_path end it 'does not display callout' do - expect(page).not_to have_content 'Open registration is enabled on your instance.' + expect(page).not_to have_content callout_title end end end @@ -42,7 +63,7 @@ RSpec.describe 'Registration enabled callout' do end it 'does not display callout' do - expect(page).not_to have_content 'Open registration is enabled on your instance.' + expect(page).not_to have_content callout_title end end end diff --git a/spec/features/groups/settings/repository_spec.rb b/spec/features/groups/settings/repository_spec.rb index d95eaf3c92c..159deb2a4e3 100644 --- a/spec/features/groups/settings/repository_spec.rb +++ b/spec/features/groups/settings/repository_spec.rb @@ -26,7 +26,7 @@ RSpec.describe 'Group Repository settings' do end end - context 'Default initial branch name' do + context 'Default branch' do before do visit group_settings_repository_path(group) end @@ -37,8 +37,8 @@ RSpec.describe 'Group Repository settings' do it 'renders the correct setting section content' do within("#js-default-branch-name") do - expect(page).to have_content("Default initial branch name") - expect(page).to have_content("The default name for the initial branch of new repositories created in the group.") + expect(page).to have_content("Default branch") + expect(page).to have_content("Set the initial name and protections for the default branch of new repositories created in the group.") end end end diff --git a/spec/features/groups/settings/user_searches_in_settings_spec.rb b/spec/features/groups/settings/user_searches_in_settings_spec.rb index abf56232aff..c7b7b25caa7 100644 --- a/spec/features/groups/settings/user_searches_in_settings_spec.rb +++ b/spec/features/groups/settings/user_searches_in_settings_spec.rb @@ -32,7 +32,7 @@ RSpec.describe 'User searches group settings', :js do visit group_settings_repository_path(group) end - it_behaves_like 'can search settings', 'Deploy tokens', 'Default initial branch name' + it_behaves_like 'can search settings', 'Deploy tokens', 'Default branch' end context 'in CI/CD page' do diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index fa6725bc294..ae1bce7ea4c 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -73,7 +73,9 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do expect(page).to have_content('New merge request') expect(page).to have_content("From #{issue.to_branch_name} into #{project.default_branch}") - expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: issue.to_branch_name, target_branch: project.default_branch })) + expect(page).to have_content("Closes ##{issue.iid}") + expect(page).to have_field("Title", with: "Draft: Resolve \"Cherry-Coloured Funk\"") + expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: issue.to_branch_name, target_branch: project.default_branch, issue_iid: issue.iid })) end end @@ -96,7 +98,9 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do expect(page).to have_content('New merge request') expect(page).to have_content("From #{branch_name} into #{project.default_branch}") - expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: branch_name, target_branch: project.default_branch })) + expect(page).to have_content("Closes ##{issue.iid}") + expect(page).to have_field("Title", with: "Draft: Resolve \"Cherry-Coloured Funk\"") + expect(page).to have_current_path(project_new_merge_request_path(project, merge_request: { source_branch: branch_name, target_branch: project.default_branch, issue_iid: issue.iid })) end end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 3210f0f9deb..05fd72a8932 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -1009,6 +1009,29 @@ RSpec.describe 'File blob', :js do stub_application_setting(static_objects_external_storage_url: 'https://cdn.gitlab.com') end + context 'private project' do + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:user) { create(:user, static_object_token: 'ABCD1234') } + + before do + project.add_developer(user) + + sign_in(user) + visit_blob('README.md') + end + + it 'shows open raw and download buttons with external storage URL prepended and user token appended to their href' do + path = project_raw_path(project, 'master/README.md') + raw_uri = "https://cdn.gitlab.com#{path}?token=#{user.static_object_token}" + download_uri = "https://cdn.gitlab.com#{path}?token=#{user.static_object_token}&inline=false" + + aggregate_failures do + expect(page).to have_link 'Open raw', href: raw_uri + expect(page).to have_link 'Download', href: download_uri + end + end + end + context 'public project' do before do visit_blob('README.md') @@ -1061,37 +1084,5 @@ RSpec.describe 'File blob', :js do end end end - - context 'when static objects external storage is enabled' do - # We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled - # This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351555 - - before do - stub_application_setting(static_objects_external_storage_url: 'https://cdn.gitlab.com') - end - - context 'private project' do - let_it_be(:project) { create(:project, :repository, :private) } - let_it_be(:user) { create(:user) } - - before do - project.add_developer(user) - - sign_in(user) - visit_blob('README.md') - end - - it 'shows open raw and download buttons with external storage URL prepended and user token appended to their href' do - path = project_raw_path(project, 'master/README.md') - raw_uri = "https://cdn.gitlab.com#{path}?token=#{user.static_object_token}" - download_uri = "https://cdn.gitlab.com#{path}?inline=false&token=#{user.static_object_token}" - - aggregate_failures do - expect(page).to have_link 'Open raw', href: raw_uri - expect(page).to have_link 'Download', href: download_uri - end - end - end - end end end diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap index 377557d2b9c..5926836d9c1 100644 --- a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap +++ b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap @@ -27,7 +27,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = ` <default-actions-stub activeviewer="simple" - rawpath="/flightjs/flight/snippets/51/raw" + rawpath="https://testing.com/flightjs/flight/snippets/51/raw" /> </div> </div> diff --git a/spec/frontend/blob/components/mock_data.js b/spec/frontend/blob/components/mock_data.js index 9a345921f16..b5803bf0cbc 100644 --- a/spec/frontend/blob/components/mock_data.js +++ b/spec/frontend/blob/components/mock_data.js @@ -22,7 +22,7 @@ export const Blob = { binary: false, name: 'dummy.md', path: 'foo/bar/dummy.md', - rawPath: '/flightjs/flight/snippets/51/raw', + rawPath: 'https://testing.com/flightjs/flight/snippets/51/raw', size: 75, simpleViewer: { ...SimpleViewerMock, diff --git a/spec/frontend/issues/create_merge_request_dropdown_spec.js b/spec/frontend/issues/create_merge_request_dropdown_spec.js index 637b4d31999..c2cfb16fdf7 100644 --- a/spec/frontend/issues/create_merge_request_dropdown_spec.js +++ b/spec/frontend/issues/create_merge_request_dropdown_spec.js @@ -59,7 +59,7 @@ describe('CreateMergeRequestDropdown', () => { describe('updateCreatePaths', () => { it('escapes branch names correctly', () => { dropdown.createBranchPath = `${TEST_HOST}/branches?branch_name=some-branch&issue=42`; - dropdown.createMrPath = `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=test&merge_request%5Btarget_branch%5D=master`; + dropdown.createMrPath = `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=test&merge_request%5Btarget_branch%5D=master&merge_request%5Bissue_iid%5D=42`; dropdown.updateCreatePaths('branch', 'contains#hash'); @@ -68,7 +68,7 @@ describe('CreateMergeRequestDropdown', () => { ); expect(dropdown.createMrPath).toBe( - `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master`, + `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master&merge_request%5Bissue_iid%5D=42`, ); }); }); diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js index 4633602de26..bff8fcda9b9 100644 --- a/spec/frontend/persistent_user_callout_spec.js +++ b/spec/frontend/persistent_user_callout_spec.js @@ -21,7 +21,8 @@ describe('PersistentUserCallout', () => { data-feature-id="${featureName}" data-group-id="${groupId}" > - <button type="button" class="js-close"></button> + <button type="button" class="js-close js-close-primary"></button> + <button type="button" class="js-close js-close-secondary"></button> </div> `; @@ -64,14 +65,15 @@ describe('PersistentUserCallout', () => { } describe('dismiss', () => { - let button; + const buttons = {}; let mockAxios; let persistentUserCallout; beforeEach(() => { const fixture = createFixture(); const container = fixture.querySelector('.container'); - button = fixture.querySelector('.js-close'); + buttons.primary = fixture.querySelector('.js-close-primary'); + buttons.secondary = fixture.querySelector('.js-close-secondary'); mockAxios = new MockAdapter(axios); persistentUserCallout = new PersistentUserCallout(container); jest.spyOn(persistentUserCallout.container, 'remove').mockImplementation(() => {}); @@ -81,29 +83,33 @@ describe('PersistentUserCallout', () => { mockAxios.restore(); }); - it('POSTs endpoint and removes container when clicking close', () => { + it.each` + button + ${'primary'} + ${'secondary'} + `('POSTs endpoint and removes container when clicking $button close', async ({ button }) => { mockAxios.onPost(dismissEndpoint).replyOnce(200); - button.click(); + buttons[button].click(); - return waitForPromises().then(() => { - expect(persistentUserCallout.container.remove).toHaveBeenCalled(); - expect(mockAxios.history.post[0].data).toBe( - JSON.stringify({ feature_name: featureName, group_id: groupId }), - ); - }); + await waitForPromises(); + + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + expect(mockAxios.history.post[0].data).toBe( + JSON.stringify({ feature_name: featureName, group_id: groupId }), + ); }); - it('invokes Flash when the dismiss request fails', () => { + it('invokes Flash when the dismiss request fails', async () => { mockAxios.onPost(dismissEndpoint).replyOnce(500); - button.click(); + buttons.primary.click(); - return waitForPromises().then(() => { - expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); - expect(createFlash).toHaveBeenCalledWith({ - message: 'An error occurred while dismissing the alert. Refresh the page and try again.', - }); + await waitForPromises(); + + expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalledWith({ + message: 'An error occurred while dismissing the alert. Refresh the page and try again.', }); }); }); @@ -132,37 +138,37 @@ describe('PersistentUserCallout', () => { mockAxios.restore(); }); - it('defers loading of a link until callout is dismissed', () => { + it('defers loading of a link until callout is dismissed', async () => { const { href, target } = deferredLink; mockAxios.onPost(dismissEndpoint).replyOnce(200); deferredLink.click(); - return waitForPromises().then(() => { - expect(windowSpy).toHaveBeenCalledWith(href, target); - expect(persistentUserCallout.container.remove).toHaveBeenCalled(); - expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName })); - }); + await waitForPromises(); + + expect(windowSpy).toHaveBeenCalledWith(href, target); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName })); }); - it('does not dismiss callout on non-deferred links', () => { + it('does not dismiss callout on non-deferred links', async () => { normalLink.click(); - return waitForPromises().then(() => { - expect(windowSpy).not.toHaveBeenCalled(); - expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); - }); + await waitForPromises(); + + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); }); - it('does not follow link when notification is closed', () => { + it('does not follow link when notification is closed', async () => { mockAxios.onPost(dismissEndpoint).replyOnce(200); button.click(); - return waitForPromises().then(() => { - expect(windowSpy).not.toHaveBeenCalled(); - expect(persistentUserCallout.container.remove).toHaveBeenCalled(); - }); + await waitForPromises(); + + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); }); }); @@ -187,30 +193,30 @@ describe('PersistentUserCallout', () => { mockAxios.restore(); }); - it('uses a link to trigger callout and defers following until callout is finished', () => { + it('uses a link to trigger callout and defers following until callout is finished', async () => { const { href } = link; mockAxios.onPost(dismissEndpoint).replyOnce(200); link.click(); - return waitForPromises().then(() => { - expect(window.location.assign).toBeCalledWith(href); - expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); - expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName })); - }); + await waitForPromises(); + + expect(window.location.assign).toBeCalledWith(href); + expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); + expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName })); }); - it('invokes Flash when the dismiss request fails', () => { + it('invokes Flash when the dismiss request fails', async () => { mockAxios.onPost(dismissEndpoint).replyOnce(500); link.click(); - return waitForPromises().then(() => { - expect(window.location.assign).not.toHaveBeenCalled(); - expect(createFlash).toHaveBeenCalledWith({ - message: - 'An error occurred while acknowledging the notification. Refresh the page and try again.', - }); + await waitForPromises(); + + expect(window.location.assign).not.toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalledWith({ + message: + 'An error occurred while acknowledging the notification. Refresh the page and try again.', }); }); }); diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index c024730570c..1c84e20c9e1 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -586,44 +586,6 @@ describe('Pipelines', () => { }); }); - describe('when the ci_runner_templates experiment is active', () => { - beforeAll(() => { - getExperimentData.mockImplementation((name) => name === 'ci_runner_templates'); - }); - - describe('the control state', () => { - beforeAll(() => { - getExperimentVariant.mockReturnValue('control'); - }); - - it('renders the CI/CD templates', () => { - expect(wrapper.findComponent(PipelinesCiTemplates).exists()).toBe(true); - }); - }); - - describe('the candidate state', () => { - beforeAll(() => { - getExperimentVariant.mockReturnValue('candidate'); - }); - - it('renders two buttons', () => { - expect(findEmptyState().findAllComponents(GlButton).length).toBe(2); - expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe( - 'Install GitLab Runners', - ); - expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe( - paths.ciRunnerSettingsPath, - ); - expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe( - 'Learn about Runners', - ); - expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe( - '/help/ci/quick_start/index.md', - ); - }); - }); - }); - it('does not render filtered search', () => { expect(findFilteredSearch().exists()).toBe(false); }); diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js index ad692a38e65..940ab05814c 100644 --- a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js +++ b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js @@ -1,19 +1,17 @@ import { GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import ExperimentTracking from '~/experimentation/experiment_tracking'; import { getBaseURL } from '~/lib/utils/url_utility'; +import { mockTracking } from 'helpers/tracking_helper'; import { - EXPERIMENT_NAME, CF_BASE_URL, TEMPLATES_BASE_URL, EASY_BUTTONS, } from '~/vue_shared/components/runner_aws_deployments/constants'; import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue'; -jest.mock('~/experimentation/experiment_tracking'); - describe('RunnerAwsDeploymentsModal', () => { let wrapper; + let trackingSpy; const findEasyButtons = () => wrapper.findAllComponents(GlLink); @@ -65,12 +63,14 @@ describe('RunnerAwsDeploymentsModal', () => { }); it('should track an event when clicked', () => { + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + findFirstButton().vm.$emit('click'); - expect(ExperimentTracking).toHaveBeenCalledWith(EXPERIMENT_NAME); - expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith( - `template_clicked_${EASY_BUTTONS[0].stackName}`, - ); + expect(trackingSpy).toHaveBeenCalledTimes(1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', { + label: EASY_BUTTONS[0].stackName, + }); }); }); }); diff --git a/spec/helpers/users/callouts_helper_spec.rb b/spec/helpers/users/callouts_helper_spec.rb index 85e11c2ed3b..71a8d340b30 100644 --- a/spec/helpers/users/callouts_helper_spec.rb +++ b/spec/helpers/users/callouts_helper_spec.rb @@ -103,6 +103,7 @@ RSpec.describe Users::CalloutsHelper do allow(helper).to receive(:current_user).and_return(admin) stub_application_setting(signup_enabled: true) allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false } + allow(helper.controller).to receive(:controller_path).and_return("admin/users") end it { is_expected.to be false } @@ -114,6 +115,7 @@ RSpec.describe Users::CalloutsHelper do allow(helper).to receive(:current_user).and_return(user) stub_application_setting(signup_enabled: true) allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false } + allow(helper.controller).to receive(:controller_path).and_return("admin/users") end it { is_expected.to be false } @@ -125,6 +127,7 @@ RSpec.describe Users::CalloutsHelper do allow(helper).to receive(:current_user).and_return(admin) stub_application_setting(signup_enabled: false) allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false } + allow(helper.controller).to receive(:controller_path).and_return("admin/users") end it { is_expected.to be false } @@ -136,17 +139,31 @@ RSpec.describe Users::CalloutsHelper do allow(helper).to receive(:current_user).and_return(admin) stub_application_setting(signup_enabled: true) allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { true } + allow(helper.controller).to receive(:controller_path).and_return("admin/users") end it { is_expected.to be false } end - context 'when not gitlab.com, `current_user` is an admin, signup is enabled, and user has not dismissed callout' do + context 'when controller path is not allowed' do before do allow(::Gitlab).to receive(:com?).and_return(false) allow(helper).to receive(:current_user).and_return(admin) stub_application_setting(signup_enabled: true) allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false } + allow(helper.controller).to receive(:controller_path).and_return("projects/issues") + end + + it { is_expected.to be false } + end + + context 'when not gitlab.com, `current_user` is an admin, signup is enabled, user has not dismissed callout, and controller path is allowed' do + before do + allow(::Gitlab).to receive(:com?).and_return(false) + allow(helper).to receive(:current_user).and_return(admin) + stub_application_setting(signup_enabled: true) + allow(helper).to receive(:user_dismissed?).with(described_class::REGISTRATION_ENABLED_CALLOUT) { false } + allow(helper.controller).to receive(:controller_path).and_return("admin/users") end it { is_expected.to be true } diff --git a/spec/views/admin/application_settings/repository.html.haml_spec.rb b/spec/views/admin/application_settings/repository.html.haml_spec.rb index 30047878b0f..e28a69d0f87 100644 --- a/spec/views/admin/application_settings/repository.html.haml_spec.rb +++ b/spec/views/admin/application_settings/repository.html.haml_spec.rb @@ -21,8 +21,9 @@ RSpec.describe 'admin/application_settings/repository.html.haml' do it 'renders the correct setting section content' do render - expect(rendered).to have_content("Default initial branch name") - expect(rendered).to have_content("The default name for the initial branch of new repositories created in the instance.") + expect(rendered).to have_content("Initial default branch name") + expect(rendered).to have_content("Set the initial name and protections for the default branch of new repositories created in the instance.") + expect(rendered).to have_content("Initial default branch protection") end end end diff --git a/workhorse/internal/testhelper/testhelper.go b/workhorse/internal/testhelper/testhelper.go index dae8f9b3149..6bbdfddcd60 100644 --- a/workhorse/internal/testhelper/testhelper.go +++ b/workhorse/internal/testhelper/testhelper.go @@ -167,3 +167,16 @@ func Retry(t testing.TB, timeout time.Duration, fn func() error) { } t.Fatalf("test timeout after %v; last error: %v", timeout, err) } + +func SetupStaticFileHelper(t *testing.T, fpath, content, directory string) string { + cwd, err := os.Getwd() + require.NoError(t, err, "get working directory") + + absDocumentRoot := path.Join(cwd, directory) + require.NoError(t, os.MkdirAll(path.Join(absDocumentRoot, path.Dir(fpath)), 0755), "create document root") + + staticFile := path.Join(absDocumentRoot, fpath) + require.NoError(t, ioutil.WriteFile(staticFile, []byte(content), 0666), "write file content") + + return absDocumentRoot +} diff --git a/workhorse/internal/upstream/.gitignore b/workhorse/internal/upstream/.gitignore new file mode 100644 index 00000000000..d63cd8b2c40 --- /dev/null +++ b/workhorse/internal/upstream/.gitignore @@ -0,0 +1 @@ +testdata/public diff --git a/workhorse/internal/upstream/routes.go b/workhorse/internal/upstream/routes.go index 59dff6d304c..b1d76dfc1bd 100644 --- a/workhorse/internal/upstream/routes.go +++ b/workhorse/internal/upstream/routes.go @@ -385,11 +385,10 @@ func configureRoutes(u *upstream) { u.route("", "^/oauth/geo/(auth|callback|logout)$", defaultUpstream), // Admin Area > Geo routes - u.route("", "^/admin/geo$", defaultUpstream), - u.route("", "^/admin/geo/", defaultUpstream), + u.route("", "^/admin/geo/replication/projects", defaultUpstream), + u.route("", "^/admin/geo/replication/designs", defaultUpstream), // Geo API routes - u.route("", "^/api/v4/geo_nodes", defaultUpstream), u.route("", "^/api/v4/geo_replication", defaultUpstream), u.route("", "^/api/v4/geo/proxy_git_ssh", defaultUpstream), u.route("", "^/api/v4/geo/graphql", defaultUpstream), @@ -397,6 +396,16 @@ func configureRoutes(u *upstream) { // Internal API routes u.route("", "^/api/v4/internal", defaultUpstream), + u.route( + "", `^/assets/`, + static.ServeExisting( + u.URLPrefix, + staticpages.CacheExpireMax, + assetsNotFoundHandler, + ), + withoutTracing(), // Tracing on assets is very noisy + ), + // Don't define a catch-all route. If a route does not match, then we know // the request should be proxied. } diff --git a/workhorse/internal/upstream/routes_test.go b/workhorse/internal/upstream/routes_test.go index f196433f5b4..8a032519bdf 100644 --- a/workhorse/internal/upstream/routes_test.go +++ b/workhorse/internal/upstream/routes_test.go @@ -2,8 +2,26 @@ package upstream import ( "testing" + + "gitlab.com/gitlab-org/gitlab/workhorse/internal/testhelper" ) +func TestAdminGeoPathsWithGeoProxy(t *testing.T) { + testCases := []testCase{ + {"Regular admin/geo", "/admin/geo", "Geo primary received request to path /admin/geo"}, + {"Specific object replication", "/admin/geo/replication/object_type", "Geo primary received request to path /admin/geo/replication/object_type"}, + {"Specific object replication per-site", "/admin/geo/sites/2/replication/object_type", "Geo primary received request to path /admin/geo/sites/2/replication/object_type"}, + {"Projects replication per-site", "/admin/geo/sites/2/replication/projects", "Geo primary received request to path /admin/geo/sites/2/replication/projects"}, + {"Designs replication per-site", "/admin/geo/sites/2/replication/designs", "Geo primary received request to path /admin/geo/sites/2/replication/designs"}, + {"Projects replication", "/admin/geo/replication/projects", "Local Rails server received request to path /admin/geo/replication/projects"}, + {"Projects replication subpaths", "/admin/geo/replication/projects/2", "Local Rails server received request to path /admin/geo/replication/projects/2"}, + {"Designs replication", "/admin/geo/replication/designs", "Local Rails server received request to path /admin/geo/replication/designs"}, + {"Designs replication subpaths", "/admin/geo/replication/designs/3", "Local Rails server received request to path /admin/geo/replication/designs/3"}, + } + + runTestCasesWithGeoProxyEnabled(t, testCases) +} + func TestProjectNotExistingGitHttpPullWithGeoProxy(t *testing.T) { testCases := []testCase{ {"secondary info/refs", "/group/project.git/info/refs", "Local Rails server received request to path /group/project.git/info/refs"}, @@ -45,3 +63,15 @@ func TestProjectNotExistingGitSSHPushWithGeoProxy(t *testing.T) { runTestCasesWithGeoProxyEnabled(t, testCases) } + +func TestAssetsServedLocallyWithGeoProxy(t *testing.T) { + path := "/assets/static.txt" + content := "local geo asset" + testhelper.SetupStaticFileHelper(t, path, content, testDocumentRoot) + + testCases := []testCase{ + {"assets path", "/assets/static.txt", "local geo asset"}, + } + + runTestCasesWithGeoProxyEnabled(t, testCases) +} diff --git a/workhorse/main_test.go b/workhorse/main_test.go index 349e2d78109..88db9e0103b 100644 --- a/workhorse/main_test.go +++ b/workhorse/main_test.go @@ -138,7 +138,7 @@ func TestDeniedXSendfileDownload(t *testing.T) { func TestAllowedStaticFile(t *testing.T) { content := "PUBLIC" - require.NoError(t, setupStaticFile("static file.txt", content)) + setupStaticFile(t, "static file.txt", content) proxied := false ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, r *http.Request) { @@ -164,7 +164,7 @@ func TestAllowedStaticFile(t *testing.T) { func TestStaticFileRelativeURL(t *testing.T) { content := "PUBLIC" - require.NoError(t, setupStaticFile("static.txt", content), "create public/static.txt") + setupStaticFile(t, "static.txt", content) ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), http.HandlerFunc(http.NotFound)) defer ts.Close() @@ -182,7 +182,7 @@ func TestStaticFileRelativeURL(t *testing.T) { func TestAllowedPublicUploadsFile(t *testing.T) { content := "PRIVATE but allowed" - require.NoError(t, setupStaticFile("uploads/static file.txt", content), "create public/uploads/static file.txt") + setupStaticFile(t, "uploads/static file.txt", content) proxied := false ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, r *http.Request) { @@ -208,7 +208,7 @@ func TestAllowedPublicUploadsFile(t *testing.T) { func TestDeniedPublicUploadsFile(t *testing.T) { content := "PRIVATE" - require.NoError(t, setupStaticFile("uploads/static.txt", content), "create public/uploads/static.txt") + setupStaticFile(t, "uploads/static.txt", content) proxied := false ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, _ *http.Request) { @@ -241,7 +241,7 @@ This is a static error page for code 499 </body> </html> ` - require.NoError(t, setupStaticFile("499.html", errorPageBody)) + setupStaticFile(t, "499.html", errorPageBody) ts := testhelper.TestServerWithHandler(nil, func(w http.ResponseWriter, _ *http.Request) { upstreamError := "499" // This is the point of the test: the size of the upstream response body @@ -266,7 +266,7 @@ This is a static error page for code 499 func TestGzipAssets(t *testing.T) { path := "/assets/static.txt" content := "asset" - require.NoError(t, setupStaticFile(path, content)) + setupStaticFile(t, path, content) buf := &bytes.Buffer{} gzipWriter := gzip.NewWriter(buf) @@ -274,7 +274,7 @@ func TestGzipAssets(t *testing.T) { require.NoError(t, err) require.NoError(t, gzipWriter.Close()) contentGzip := buf.String() - require.NoError(t, setupStaticFile(path+".gz", contentGzip)) + setupStaticFile(t, path+".gz", contentGzip) proxied := false ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, r *http.Request) { @@ -319,7 +319,7 @@ func TestGzipAssets(t *testing.T) { func TestAltDocumentAssets(t *testing.T) { path := "/assets/static.txt" content := "asset" - require.NoError(t, setupAltStaticFile(path, content)) + setupAltStaticFile(t, path, content) buf := &bytes.Buffer{} gzipWriter := gzip.NewWriter(buf) @@ -327,7 +327,7 @@ func TestAltDocumentAssets(t *testing.T) { require.NoError(t, err) require.NoError(t, gzipWriter.Close()) contentGzip := buf.String() - require.NoError(t, setupAltStaticFile(path+".gz", contentGzip)) + setupAltStaticFile(t, path+".gz", contentGzip) proxied := false ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, r *http.Request) { @@ -712,25 +712,12 @@ func TestRejectUnknownMethod(t *testing.T) { require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) } -func setupStaticFile(fpath, content string) error { - return setupStaticFileHelper(fpath, content, testDocumentRoot) +func setupStaticFile(t *testing.T, fpath, content string) { + absDocumentRoot = testhelper.SetupStaticFileHelper(t, fpath, content, testDocumentRoot) } -func setupAltStaticFile(fpath, content string) error { - return setupStaticFileHelper(fpath, content, testAltDocumentRoot) -} - -func setupStaticFileHelper(fpath, content, directory string) error { - cwd, err := os.Getwd() - if err != nil { - return err - } - absDocumentRoot = path.Join(cwd, directory) - if err := os.MkdirAll(path.Join(absDocumentRoot, path.Dir(fpath)), 0755); err != nil { - return err - } - staticFile := path.Join(absDocumentRoot, fpath) - return ioutil.WriteFile(staticFile, []byte(content), 0666) +func setupAltStaticFile(t *testing.T, fpath, content string) { + absDocumentRoot = testhelper.SetupStaticFileHelper(t, fpath, content, testAltDocumentRoot) } func prepareDownloadDir(t *testing.T) { @@ -896,7 +883,7 @@ This is a static error page for code 503 </body> </html> ` - require.NoError(t, setupStaticFile("503.html", errorPageBody)) + setupStaticFile(t, "503.html", errorPageBody) ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("X-Gitlab-Custom-Error", "1") |