diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml index 8b7691045cb..217da6506bf 100644 --- a/.gitlab/ci/docs.gitlab-ci.yml +++ b/.gitlab/ci/docs.gitlab-ci.yml @@ -44,7 +44,7 @@ docs-lint markdown: - .default-retry - .docs:rules:docs-lint # When updating the image version here, update it in /scripts/lint-doc.sh too. - image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.15-vale-2.15.0-markdownlint-0.31.0 + image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.15-vale-2.15.5-markdownlint-0.31.1 stage: lint needs: [] script: diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml index a955096992f..d2192a7511a 100644 --- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml @@ -1,3 +1,10 @@ +include: + - project: gitlab-org/quality/pipeline-common + ref: 0.3.6 + file: + - /ci/allure-report.yml + - /ci/knapsack-report.yml + .review-qa-base: extends: - .use-docker-in-docker @@ -43,27 +50,13 @@ when: always .allure-report-base: - image: - name: ${GITLAB_DEPENDENCY_PROXY}andrcuns/allure-report-publisher:0.4.2 - entrypoint: [""] + extends: .generate-allure-report-base stage: post-qa variables: - GIT_STRATEGY: none - STORAGE_CREDENTIALS: $QA_ALLURE_REPORT_GCS_CREDENTIALS GITLAB_AUTH_TOKEN: $GITLAB_QA_MR_ALLURE_REPORT_TOKEN ALLURE_PROJECT_PATH: $CI_PROJECT_PATH ALLURE_MERGE_REQUEST_IID: $CI_MERGE_REQUEST_IID - allow_failure: true - script: - - | - allure-report-publisher upload gcs \ - --results-glob="qa/tmp/allure-results/*" \ - --bucket="gitlab-qa-allure-reports" \ - --prefix="$ALLURE_REPORT_PATH_PREFIX/$CI_COMMIT_REF_SLUG" \ - --update-pr="comment" \ - --copy-latest \ - --ignore-missing-results \ - --color + ALLURE_RESULTS_GLOB: qa/tmp/allure-results/* review-qa-smoke: extends: @@ -121,23 +114,19 @@ review-performance: performance: performance.json expire_in: 31d -allure-report-qa-smoke: +# Generate single report for both smoke and reliable test jobs +# Both job types are essentially the same: +# * always executed +# * always blocking +allure-report-qa-blocking: extends: - .allure-report-base - - .review:rules:review-qa-smoke-report - needs: ["review-qa-smoke"] + - .review:rules:review-qa-blocking-report + needs: + - review-qa-smoke + - review-qa-reliable variables: - ALLURE_REPORT_PATH_PREFIX: gitlab-review-smoke - ALLURE_JOB_NAME: review-qa-smoke - -allure-report-qa-reliable: - extends: - - .allure-report-base - - .review:rules:review-qa-reliable-report - needs: ["review-qa-reliable"] - variables: - ALLURE_REPORT_PATH_PREFIX: gitlab-review-reliable - ALLURE_JOB_NAME: review-qa-reliable + ALLURE_JOB_NAME: review-qa-blocking allure-report-qa-all: extends: @@ -145,18 +134,11 @@ allure-report-qa-all: - .review:rules:review-qa-all-report needs: ["review-qa-all"] variables: - ALLURE_REPORT_PATH_PREFIX: gitlab-review-all ALLURE_JOB_NAME: review-qa-all knapsack-report: extends: - - .review:rules:knapsack-report - image: - name: ${QA_IMAGE} - entrypoint: [""] + - .generate-knapsack-report-base stage: post-qa - allow_failure: true - before_script: - - cd qa - script: - - bundle exec rake 'knapsack:upload[tmp/knapsack/*/*.json]' + variables: + QA_KNAPSACK_REPORT_FILE_PATTERN: $CI_PROJECT_DIR/tmp/knapsack/*/*.json diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 8824c333f96..2494fea94a6 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1583,17 +1583,12 @@ # # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76756 -# Since `review-qa-smoke` isn't allowed to fail, we need to use `when: always` for `review-qa-smoke-report`. -.review:rules:review-qa-smoke-report: - rules: - - when: always - .review:rules:review-qa-reliable: rules: - when: on_success # Since `review-qa-reliable` isn't allowed to fail, we need to use `when: always`for `review-qa-reliable-report`. -.review:rules:review-qa-reliable-report: +.review:rules:review-qa-blocking-report: rules: - when: always @@ -1613,11 +1608,6 @@ - when: on_success - when: on_failure -.review:rules:knapsack-report: - rules: - - if: '$KNAPSACK_GENERATE_REPORT == "true"' - when: always - .review:rules:review-cleanup: rules: - <<: *if-not-ee diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 43c989b5531..373aea97570 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.56.1 +1.57.0 diff --git a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue index 4e65d2d5055..025c48f355d 100644 --- a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue +++ b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue @@ -12,7 +12,17 @@ import { mapState, mapGetters } from 'vuex'; import { s__ } from '~/locale'; import highlight from '~/lib/utils/highlight'; import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; -import { GROUPS_CATEGORY, PROJECTS_CATEGORY, LARGE_AVATAR_PX, SMALL_AVATAR_PX } from '../constants'; +import { truncateNamespace } from '~/lib/utils/text_utility'; + +import { + GROUPS_CATEGORY, + PROJECTS_CATEGORY, + MERGE_REQUEST_CATEGORY, + ISSUES_CATEGORY, + RECENT_EPICS_CATEGORY, + LARGE_AVATAR_PX, + SMALL_AVATAR_PX, +} from '../constants'; export default { name: 'HeaderSearchAutocompleteItems', @@ -40,7 +50,7 @@ export default { }, }, computed: { - ...mapState(['search', 'loading', 'autocompleteError']), + ...mapState(['search', 'loading', 'autocompleteError', 'searchContext']), ...mapGetters(['autocompleteGroupedSearchOptions']), }, watch: { @@ -53,6 +63,13 @@ export default { }, }, methods: { + truncateNamespace(string) { + if (string.split(' / ').length > 2) { + return truncateNamespace(string); + } + + return string; + }, highlightedName(val) { return highlight(val, this.search); }, @@ -66,6 +83,35 @@ export default { isOptionFocused(data) { return this.currentFocusedOption?.html_id === data.html_id; }, + isProjectsCategory(data) { + return data.category === PROJECTS_CATEGORY; + }, + getEntityId(data) { + switch (data.category) { + case GROUPS_CATEGORY: + case RECENT_EPICS_CATEGORY: + return data.group_id || data.id || this.searchContext?.group?.id; + case PROJECTS_CATEGORY: + case ISSUES_CATEGORY: + case MERGE_REQUEST_CATEGORY: + return data.project_id || data.id || this.searchContext?.project?.id; + default: + return data.id; + } + }, + getEntitytName(data) { + switch (data.category) { + case GROUPS_CATEGORY: + case RECENT_EPICS_CATEGORY: + return data.group_name || data.value || data.label || this.searchContext?.group?.name; + case PROJECTS_CATEGORY: + case ISSUES_CATEGORY: + case MERGE_REQUEST_CATEGORY: + return data.project_name || data.value || data.label || this.searchContext?.project?.name; + default: + return data.label; + } + }, }, AVATAR_SHAPE_OPTION_RECT, }; @@ -92,12 +138,22 @@ export default { - + + + + diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js index 9cb08605444..045a552efb0 100644 --- a/app/assets/javascripts/header_search/constants.js +++ b/app/assets/javascripts/header_search/constants.js @@ -20,6 +20,12 @@ export const GROUPS_CATEGORY = 'Groups'; export const PROJECTS_CATEGORY = 'Projects'; +export const ISSUES_CATEGORY = 'Recent issues'; + +export const MERGE_REQUEST_CATEGORY = 'Recent merge requests'; + +export const RECENT_EPICS_CATEGORY = 'Recent epics'; + export const LARGE_AVATAR_PX = 32; export const SMALL_AVATAR_PX = 16; diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue index 005c3bcd0e3..1fc40e5c0d6 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue @@ -43,7 +43,9 @@ export default { message: s__( 'Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}', ), - linkUrl: helpPagePath('integration/jira_development_panel.html', { anchor: 'usage' }), + linkUrl: helpPagePath('integration/jira_development_panel.html', { + anchor: 'use-the-integration', + }), variant: 'success', }); diff --git a/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue index 754908c9385..1eb383a1904 100644 --- a/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue +++ b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue @@ -60,7 +60,8 @@ export default { {{ description }} - {{ __('IP Address') }} {{ ipAddress }} + {{ __('IP Address') }} + {{ ipAddress }} diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 93e56867780..e5d793b1099 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -293,7 +293,7 @@ class GroupsController < Groups::ApplicationController :setup_for_company, :jobs_to_be_done, :crm_enabled - ] + ] + [group_feature_attributes: group_feature_attributes] end # rubocop: disable CodeReuse/ActiveRecord @@ -396,6 +396,10 @@ class GroupsController < Groups::ApplicationController experiment(:require_verification_for_namespace_creation, user: current_user).track(:start_create_group) end + + def group_feature_attributes + [] + end end GroupsController.prepend_mod_with('GroupsController') diff --git a/app/experiments/ios_specific_templates_experiment.rb b/app/experiments/ios_specific_templates_experiment.rb new file mode 100644 index 00000000000..1731fa87be8 --- /dev/null +++ b/app/experiments/ios_specific_templates_experiment.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class IosSpecificTemplatesExperiment < ApplicationExperiment + before_run(if: :skip_experiment) { throw(:abort) } # rubocop:disable Cop/BanCatchThrow + + private + + def skip_experiment + actor_not_able_to_create_pipelines? || + project_targets_non_ios_platforms? || + project_has_gitlab_ci? || + project_has_pipelines? + end + + def actor_not_able_to_create_pipelines? + !context.actor.is_a?(User) || !context.actor.can?(:create_pipeline, context.project) + end + + def project_targets_non_ios_platforms? + context.project.project_setting.target_platforms.exclude?('ios') + end + + def project_has_gitlab_ci? + context.project.has_ci? && context.project.builds_enabled? + end + + def project_has_pipelines? + context.project.all_pipelines.count > 0 + end +end diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb index 8d2f83409be..70d2a4fafd1 100644 --- a/app/helpers/ci/pipelines_helper.rb +++ b/app/helpers/ci/pipelines_helper.rb @@ -106,6 +106,12 @@ module Ci e.candidate { data[:any_runners_available] = project.active_runners.exists?.to_s } end + experiment(:ios_specific_templates, actor: current_user, project: project, sticky_to: project) do |e| + e.candidate do + data[:registration_token] = project.runners_token if can?(current_user, :register_project_runners, project) + end + end + data end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8586e93c88f..f8bfc74b344 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -260,6 +260,7 @@ module SearchHelper { category: "Groups", id: group.id, + value: "#{search_result_sanitize(group.name)}", label: "#{search_result_sanitize(group.full_name)}", url: group_path(group), avatar_url: group.avatar_url || '' @@ -311,7 +312,9 @@ module SearchHelper id: mr.id, label: search_result_sanitize(mr.title), url: merge_request_path(mr), - avatar_url: mr.project.avatar_url || '' + avatar_url: mr.project.avatar_url || '', + project_id: mr.target_project_id, + project_name: mr.target_project.name } end end @@ -325,7 +328,9 @@ module SearchHelper id: i.id, label: search_result_sanitize(i.title), url: issue_path(i), - avatar_url: i.project.avatar_url || '' + avatar_url: i.project.avatar_url || '', + project_id: i.project_id, + project_name: i.project.name } end end diff --git a/app/models/project.rb b/app/models/project.rb index c15c3a65e74..6e5c694cb9c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1946,6 +1946,10 @@ class Project < ApplicationRecord Gitlab.config.pages.enabled end + def pages_show_onboarding? + !(pages_metadatum&.onboarding_complete || pages_metadatum&.deployed) + end + def remove_private_deploy_keys exclude_keys_linked_to_other_projects = <<-SQL NOT EXISTS ( @@ -1961,6 +1965,10 @@ class Project < ApplicationRecord .delete_all end + def mark_pages_onboarding_complete + ensure_pages_metadatum.update!(onboarding_complete: true) + end + def mark_pages_as_deployed ensure_pages_metadatum.update!(deployed: true) end diff --git a/app/models/user.rb b/app/models/user.rb index 4610b5a9398..68c1b1c5549 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -476,7 +476,7 @@ class User < ApplicationRecord scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil) } scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) } scope :by_ids_or_usernames, -> (ids, usernames) { where(username: usernames).or(where(id: ids)) } - scope :without_forbidden_states, -> { confirmed.where.not(state: FORBIDDEN_SEARCH_STATES) } + scope :without_forbidden_states, -> { where.not(state: FORBIDDEN_SEARCH_STATES) } strip_attributes! :name @@ -1732,8 +1732,12 @@ class User < ApplicationRecord end def attention_requested_open_merge_requests_count(force: false) - Rails.cache.fetch(attention_request_cache_key, force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do + if Feature.enabled?(:uncached_mr_attention_requests_count, self, default_enabled: :yaml) MergeRequestsFinder.new(self, attention: self.username, state: 'opened', non_archived: true).execute.count + else + Rails.cache.fetch(attention_request_cache_key, force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do + MergeRequestsFinder.new(self, attention: self.username, state: 'opened', non_archived: true).execute.count + end end end diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb index e8fc18e6cf3..9fd50c8c51d 100644 --- a/app/serializers/merge_request_serializer.rb +++ b/app/serializers/merge_request_serializer.rb @@ -27,5 +27,3 @@ class MergeRequestSerializer < BaseSerializer super(merge_request, opts, entity) end end - -MergeRequestSerializer.prepend_mod_with('MergeRequestSerializer') diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb index a16f8bbd367..3e15d47e8af 100644 --- a/app/services/jira/requests/base.rb +++ b/app/services/jira/requests/base.rb @@ -68,7 +68,7 @@ module Jira end def auth_docs_link_start - auth_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira', anchor: 'authentication-in-jira') + auth_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira/index', anchor: 'authentication-in-jira') ''.html_safe % { url: auth_docs_link_url } end diff --git a/app/views/admin/application_settings/_git_lfs_limits.html.haml b/app/views/admin/application_settings/_git_lfs_limits.html.haml index de5a2ceaa3d..b8970a5bcf1 100644 --- a/app/views/admin/application_settings/_git_lfs_limits.html.haml +++ b/app/views/admin/application_settings/_git_lfs_limits.html.haml @@ -1,16 +1,13 @@ -= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-git-lfs-limits-settings'), html: { class: 'fieldset-form' } do |f| += gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-git-lfs-limits-settings'), html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset %h5 = _('Authenticated Git LFS request rate limit') .form-group - .form-check - = f.check_box :throttle_authenticated_git_lfs_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_authenticated_git_lfs_checkbox' } - = f.label :throttle_authenticated_git_lfs_enabled, class: 'form-check-label gl-font-weight-bold' do - = _('Enable authenticated Git LFS request rate limit') - %span.form-text.gl-text-gray-600 - = _('Helps reduce request volume (for example, from crawlers or abusive bots)') + = f.gitlab_ui_checkbox_component :throttle_authenticated_git_lfs_enabled, + _('Enable authenticated Git LFS request rate limit'), + help_text: _('Helps reduce request volume (for example, from crawlers or abusive bots)') .form-group = f.label :throttle_authenticated_git_lfs_requests_per_period, _('Max authenticated Git LFS requests per period per user'), class: 'gl-font-weight-bold' = f.number_field :throttle_authenticated_git_lfs_requests_per_period, class: 'form-control gl-form-input' diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml index a1990ad5750..925b3681298 100644 --- a/app/views/admin/applications/_form.html.haml +++ b/app/views/admin/applications/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [:admin, @application], url: @url, html: {role: 'form'} do |f| += gitlab_ui_form_for [:admin, @application], url: @url, html: {role: 'form'} do |f| = form_errors(application) = content_tag :div, class: 'form-group row' do @@ -45,7 +45,7 @@ .col-sm-2.col-form-label.pt-0 = f.label :scopes .col-sm-10 - = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes + = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes, f: f .form-actions = f.submit _('Save application'), class: "gl-button btn btn-confirm wide" diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 5f12cd048b3..8ac6f63cdfb 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -18,7 +18,7 @@ .form-group.row .offset-sm-2.col-sm-10 - = render 'shared/allow_request_access', form: f, bold_label: true + = render 'shared/allow_request_access', form: f = render 'groups/group_admin_settings', f: f diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml index ab6861b5f24..785ca71b371 100644 --- a/app/views/groups/_group_admin_settings.html.haml +++ b/app/views/groups/_group_admin_settings.html.haml @@ -2,14 +2,12 @@ .col-sm-2.col-form-label.pt-0 = f.label :lfs_enabled, _('Large File Storage') .col-sm-10 - .form-check - = f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input' - = f.label :lfs_enabled, class: 'form-check-label' do - %strong - = _('Allow projects within this group to use Git LFS') - = link_to sprite_icon('question-o'), help_page_path('topics/git/lfs/index') - %br/ - %span= _('This setting can be overridden in each project.') + - label = _('Allow projects within this group to use Git LFS') + - help_link = link_to sprite_icon('question-o'), help_page_path('topics/git/lfs/index'), class: 'gl-ml-2' + = f.gitlab_ui_checkbox_component :lfs_enabled, + '%{label}%{help_link}'.html_safe % { label: label, help_link: help_link }, + help_text: _('This setting can be overridden in each project.'), + checkbox_options: { checked: @group.lfs_enabled? } .form-group.row .col-sm-2.col-form-label = f.label s_('ProjectCreationLevel|Allowed to create projects') @@ -26,12 +24,9 @@ .col-sm-2.col-form-label.pt-0 = f.label :require_two_factor_authentication, _('Two-factor authentication') .col-sm-10 - .form-check - = f.check_box :require_two_factor_authentication, class: 'form-check-input' - = f.label :require_two_factor_authentication, class: 'form-check-label' do - %strong - = _("Require all users in this group to set up two-factor authentication") - = link_to sprite_icon('question-o'), help_page_path('security/two_factor_authentication', anchor: 'enforce-2fa-for-all-users-in-a-group') + - label = _("Require all users in this group to set up two-factor authentication") + - help_link = link_to sprite_icon('question-o'), help_page_path('security/two_factor_authentication', anchor: 'enforce-2fa-for-all-users-in-a-group'), class: 'gl-ml-2' + = f.gitlab_ui_checkbox_component :require_two_factor_authentication, '%{label}%{help_link}'.html_safe % { label: label, help_link: help_link } .form-group.row .offset-sm-2.col-sm-10 .form-check diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index dd62c9e118d..1a2f770cd59 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -34,6 +34,8 @@ = render 'groups/settings/ip_restriction_registration_features_cta', f: f = render_if_exists 'groups/settings/ip_restriction', f: f, group: @group = render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group + - if Feature.enabled?(:group_wiki_settings_toggle, @group, default_enabled: :yaml) + = render_if_exists 'groups/settings/wiki', f: f, group: @group = render 'groups/settings/lfs', f: f = render 'groups/settings/project_creation_level', f: f, group: @group = render 'groups/settings/subgroup_creation_level', f: f, group: @group diff --git a/app/views/shared/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml index ca09fd39dc1..608a0ca37d9 100644 --- a/app/views/shared/_allow_request_access.html.haml +++ b/app/views/shared/_allow_request_access.html.haml @@ -1,6 +1,3 @@ -- label_class = local_assigns.fetch(:bold_label, false) ? 'font-weight-bold' : '' - = form.gitlab_ui_checkbox_component :request_access_enabled, _('Allow users to request access (if visibility is public or internal)'), - label_options: { class: label_class }, checkbox_options: { data: { qa_selector: 'request_access_checkbox' } } diff --git a/app/views/shared/access_tokens/_form.html.haml b/app/views/shared/access_tokens/_form.html.haml index 0b68cfe65e5..d4106ba4e5d 100644 --- a/app/views/shared/access_tokens/_form.html.haml +++ b/app/views/shared/access_tokens/_form.html.haml @@ -10,7 +10,7 @@ %p.profile-settings-content = _("Enter the name of your application, and we'll return a unique %{type}.") % { type: type } -= form_for token, as: prefix, url: path, method: :post, html: { class: 'js-requires-input' } do |f| += gitlab_ui_form_for token, as: prefix, url: path, method: :post, html: { class: 'js-requires-input' } do |f| = form_errors(token) @@ -42,7 +42,7 @@ %p.text-secondary#select_scope_help_text = s_('Tokens|Scopes set the permission levels granted to the token.') = link_to _("Learn more."), help_path, target: '_blank', rel: 'noopener noreferrer' - = render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes + = render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes, f: f - if prefix == :personal_access_token && Feature.enabled?(:personal_access_tokens_scoped_to_projects, current_user) .js-access-tokens-projects diff --git a/app/views/shared/deploy_keys/_project_group_form.html.haml b/app/views/shared/deploy_keys/_project_group_form.html.haml index 8da48a7936a..c9edf09b350 100644 --- a/app/views/shared/deploy_keys/_project_group_form.html.haml +++ b/app/views/shared/deploy_keys/_project_group_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace, @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input container" } do |f| += gitlab_ui_form_for [@project.namespace, @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input container" } do |f| = form_errors(@deploy_keys.new_key) .form-group.row = f.label :title, class: "label-bold" @@ -13,12 +13,8 @@ = f.fields_for :deploy_keys_projects do |deploy_keys_project_form| .form-group.row - = deploy_keys_project_form.label :can_push do - = deploy_keys_project_form.check_box :can_push - %strong= _('Grant write permissions to this key') - .form-group.row - %p.light.gl-mb-0 - = _('Allow this key to push to this repository') + = deploy_keys_project_form.gitlab_ui_checkbox_component :can_push, _('Grant write permissions to this key'), + help_text: _('Allow this key to push to this repository') .form-group.row = f.submit _("Add key"), class: "btn gl-button btn-confirm", data: { qa_selector: "add_deploy_key_button"} diff --git a/app/views/shared/doorkeeper/applications/_form.html.haml b/app/views/shared/doorkeeper/applications/_form.html.haml index adfd7ea98b7..c1650405776 100644 --- a/app/views/shared/doorkeeper/applications/_form.html.haml +++ b/app/views/shared/doorkeeper/applications/_form.html.haml @@ -1,4 +1,4 @@ -= form_for @application, url: url, html: { role: 'form', class: 'doorkeeper-app-form' } do |f| += gitlab_ui_form_for @application, url: url, html: { role: 'form', class: 'doorkeeper-app-form' } do |f| = form_errors(@application) .form-group @@ -12,22 +12,19 @@ %span.form-text.text-muted = _('Use one line per URI') - .form-group.form-check - = f.check_box :confidential, class: 'form-check-input' - = f.label :confidential, class: 'label-bold form-check-label' - %span.form-text.text-muted - = _('Enable only for confidential applications exclusively used by a trusted backend server that can securely store the client secret. Do not enable for native-mobile, single-page, or other JavaScript applications because they cannot keep the client secret confidential.') + .form-group + = f.gitlab_ui_checkbox_component :confidential, _('Confidential'), + help_text: _('Enable only for confidential applications exclusively used by a trusted backend server that can securely store the client secret. Do not enable for native-mobile, single-page, or other JavaScript applications because they cannot keep the client secret confidential.') - .form-group.form-check - = f.check_box :expire_access_tokens, class: 'form-check-input' - = f.label :expire_access_tokens, class: 'label-bold form-check-label' - %span.form-text.text-muted - = _('Enable access tokens to expire after 2 hours. If disabled, tokens do not expire.') - = link_to _('Learn more.'), help_page_path('integration/oauth_provider.md', anchor: 'expiring-access-tokens'), target: '_blank', rel: 'noopener noreferrer' + .form-group + - help_text = _('Enable access tokens to expire after 2 hours. If disabled, tokens do not expire.') + - help_link = link_to _('Learn more.'), help_page_path('integration/oauth_provider.md', anchor: 'expiring-access-tokens'), target: '_blank', rel: 'noopener noreferrer' + = f.gitlab_ui_checkbox_component :expire_access_tokens, _('Expire access tokens'), + help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link } .form-group = f.label :scopes, class: 'label-bold' - = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: @application, scopes: @scopes + = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: @application, scopes: @scopes, f: f .gl-mt-3 = f.submit _('Save application'), class: "gl-button btn btn-confirm" diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml index 33e95446bd7..010376464f1 100644 --- a/app/views/shared/tokens/_scopes_form.html.haml +++ b/app/views/shared/tokens/_scopes_form.html.haml @@ -1,9 +1,14 @@ - scopes = local_assigns.fetch(:scopes) - prefix = local_assigns.fetch(:prefix) - token = local_assigns.fetch(:token) +- f = local_assigns.fetch(:f) -- scopes.each do |scope| - %fieldset.form-group.form-check - = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input", data: { qa_selector: "#{scope}_checkbox" } - = label_tag "#{prefix}_scopes_#{scope}", scope, class: 'label-bold form-check-label' - .text-secondary= t scope, scope: scope_description(prefix) +%fieldset + - scopes.each do |scope| + - help_text = t scope, scope: scope_description(prefix) + = f.gitlab_ui_checkbox_component :scopes, scope, + help_text: help_text, + checkbox_options: { checked: token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", multiple: true, data: { qa_selector: "#{scope}_checkbox" } }, + checked_value: scope, + unchecked_value: nil, + label_options: { data: { qa_selector: "#{scope}_label" } } diff --git a/config/feature_flags/development/group_wiki_settings_toggle.yml b/config/feature_flags/development/group_wiki_settings_toggle.yml new file mode 100644 index 00000000000..083453a6944 --- /dev/null +++ b/config/feature_flags/development/group_wiki_settings_toggle.yml @@ -0,0 +1,8 @@ +--- +name: group_wiki_settings_toggle +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82298 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358387 +milestone: '14.10' +type: development +group: group::editor +default_enabled: false diff --git a/config/feature_flags/development/uncached_mr_attention_requests_count.yml b/config/feature_flags/development/uncached_mr_attention_requests_count.yml new file mode 100644 index 00000000000..239490ab1c2 --- /dev/null +++ b/config/feature_flags/development/uncached_mr_attention_requests_count.yml @@ -0,0 +1,8 @@ +--- +name: uncached_mr_attention_requests_count +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84145 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357480 +milestone: '14.10' +type: development +group: group::code review +default_enabled: false diff --git a/config/feature_flags/experiment/ios_specific_templates.yml b/config/feature_flags/experiment/ios_specific_templates.yml new file mode 100644 index 00000000000..0af80e7a5bb --- /dev/null +++ b/config/feature_flags/experiment/ios_specific_templates.yml @@ -0,0 +1,8 @@ +--- +name: ios_specific_templates +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84589 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/356398 +milestone: "14.10" +type: experiment +group: group::activation +default_enabled: false diff --git a/config/metrics/counts_28d/20220407183012_count_distinct_user_id_from_deployment_approvals.yml b/config/metrics/counts_28d/20220407183012_count_distinct_user_id_from_deployment_approvals.yml new file mode 100644 index 00000000000..7b594efbf19 --- /dev/null +++ b/config/metrics/counts_28d/20220407183012_count_distinct_user_id_from_deployment_approvals.yml @@ -0,0 +1,21 @@ +--- +key_path: usage_activity_by_stage_monthly.release.users_creating_deployment_approvals +description: Users who have used the deployment approvals feature by month +product_section: ops +product_stage: release +product_group: group::release +product_category: continuous_delivery +value_type: number +status: active +milestone: "14.10" +introduced_by_url: +time_frame: 28d +data_source: database +data_category: optional +instrumentation_class: CountUsersDeploymentApprovals +performance_indicator_type: [] +distribution: +- ee +tier: +- premium +- ultimate diff --git a/config/metrics/counts_all/20220407183012_count_distinct_user_id_from_deployment_approvals.yml b/config/metrics/counts_all/20220407183012_count_distinct_user_id_from_deployment_approvals.yml new file mode 100644 index 00000000000..902347d9f0b --- /dev/null +++ b/config/metrics/counts_all/20220407183012_count_distinct_user_id_from_deployment_approvals.yml @@ -0,0 +1,21 @@ +--- +key_path: usage_activity_by_stage.release.users_creating_deployment_approvals +description: Users who have used the deployment approvals feature by all time +product_section: ops +product_stage: release +product_group: group::release +product_category: continuous_delivery +value_type: number +status: active +milestone: "14.10" +introduced_by_url: +time_frame: all +data_source: database +data_category: optional +instrumentation_class: CountUsersDeploymentApprovals +performance_indicator_type: [] +distribution: +- ee +tier: +- premium +- ultimate diff --git a/data/removals/15_0/15-0-rerequest-review.yml b/data/removals/15_0/15-0-rerequest-review.yml deleted file mode 100644 index e692df05670..00000000000 --- a/data/removals/15_0/15-0-rerequest-review.yml +++ /dev/null @@ -1,16 +0,0 @@ -- name: "Request a new review" # the name of the feature being removed. Avoid the words `deprecation`, `deprecate`, `removal`, and `remove` in this field because these are implied. - announcement_milestone: "15.0" # The milestone when this feature was deprecated. - announcement_date: "2022-05-22" # The date of the milestone release when this feature was deprecated. This should almost always be the 22nd of a month (YYYY-MM-DD), unless you did an out of band blog post. - removal_milestone: "15.0" # The milestone when this feature is being removed. - removal_date: "2022-05-22" # This should almost always be the 22nd of a month (YYYY-MM-DD), the date of the milestone release when this feature will be removed. - breaking_change: false # Change to true if this removal is a breaking change. - reporter: phikai # GitLab username of the person reporting the removal - body: | # Do not modify this line, instead modify the lines below. - The ability to [request a new review](https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#request-a-new-review) has been removed in GitLab 15.0. This feature is replaced with [requesting attention](https://docs.gitlab.com/ee/user/project/merge_requests/#request-attention-to-a-merge-request) to a merge request. -# The following items are not published on the docs page, but may be used in the future. - stage: Create # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth - tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate] - issue_url: # (optional) This is a link to the deprecation issue in GitLab - documentation_url: https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#request-a-new-review # (optional) This is a link to the current documentation page - image_url: # (optional) This is a link to a thumbnail image depicting the feature - video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg diff --git a/db/migrate/20220318141037_add_pages_onboarding_state.rb b/db/migrate/20220318141037_add_pages_onboarding_state.rb new file mode 100644 index 00000000000..e320bee63c4 --- /dev/null +++ b/db/migrate/20220318141037_add_pages_onboarding_state.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddPagesOnboardingState < Gitlab::Database::Migration[1.0] + def up + add_column :project_pages_metadata, :onboarding_complete, :boolean, default: false, null: false + end + + def down + remove_column :project_pages_metadata, :onboarding_complete + end +end diff --git a/db/post_migrate/20220322132242_update_pages_onboarding_state.rb b/db/post_migrate/20220322132242_update_pages_onboarding_state.rb new file mode 100644 index 00000000000..896ab78a266 --- /dev/null +++ b/db/post_migrate/20220322132242_update_pages_onboarding_state.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class UpdatePagesOnboardingState < Gitlab::Database::Migration[1.0] + disable_ddl_transaction! + BATCH_SIZE = 75 + + def up + define_batchable_model( + :project_pages_metadata + ).where( + deployed: true + ).each_batch( + of: BATCH_SIZE, + column: :project_id + ) do |batch| + batch.update_all(onboarding_complete: true) + end + end + + def down + define_batchable_model( + :project_pages_metadata + ).where( + onboarding_complete: true + ).each_batch( + of: BATCH_SIZE, + column: :project_id + ) do |batch| + batch.update_all(onboarding_complete: false) + end + end +end diff --git a/db/schema_migrations/20220318141037 b/db/schema_migrations/20220318141037 new file mode 100644 index 00000000000..e2451a42392 --- /dev/null +++ b/db/schema_migrations/20220318141037 @@ -0,0 +1 @@ +d9a9d143ff553cbad5eb32a908370133549850f10b27b30eb6a1bde686054c45 \ No newline at end of file diff --git a/db/schema_migrations/20220322132242 b/db/schema_migrations/20220322132242 new file mode 100644 index 00000000000..f21f62874a7 --- /dev/null +++ b/db/schema_migrations/20220322132242 @@ -0,0 +1 @@ +94dbae9bbfb2da49a0d20b674e15f457c613c8ba0612603dd8fe9ac5699160d1 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 9535e7deab0..771fce97e59 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19304,7 +19304,8 @@ ALTER SEQUENCE project_mirror_data_id_seq OWNED BY project_mirror_data.id; CREATE TABLE project_pages_metadata ( project_id bigint NOT NULL, deployed boolean DEFAULT false NOT NULL, - pages_deployment_id bigint + pages_deployment_id bigint, + onboarding_complete boolean DEFAULT false NOT NULL ); CREATE TABLE project_repositories ( diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md index 5a9699b3a39..cdf6d48ad41 100644 --- a/doc/administration/integration/plantuml.md +++ b/doc/administration/integration/plantuml.md @@ -213,7 +213,7 @@ After configuring your local PlantUML server, you're ready to enable the PlantUM 1. On the left sidebar, go to **Settings > General** and expand the **PlantUML** section. 1. Select the **Enable PlantUML** checkbox. 1. Set the PlantUML instance as `https://gitlab.example.com/-/plantuml/`, - and click **Save changes**. + and select **Save changes**. Depending on your PlantUML and GitLab version numbers, you may also need to take these steps: diff --git a/doc/api/users.md b/doc/api/users.md index e9fe6bf7dfe..7b4962735e6 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -305,8 +305,11 @@ Parameters: "website_url": "", "organization": "", "job_title": "Operations Specialist", + "pronouns": "he/him", + "work_information": null, "followers": 1, - "following": 1 + "following": 1, + "local_time": "3:38 PM" } ``` @@ -346,6 +349,11 @@ Example Responses: "website_url": "", "organization": "", "job_title": "Operations Specialist", + "pronouns": "he/him", + "work_information": null, + "followers": 1, + "following": 1, + "local_time": "3:38 PM", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", "theme_id": 1, @@ -580,6 +588,13 @@ GET /user "twitter": "", "website_url": "", "organization": "", + "job_title": "", + "pronouns": "he/him", + "bot": false, + "work_information": null, + "followers": 0, + "following": 0, + "local_time": "3:38 PM", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", "theme_id": 1, @@ -596,10 +611,13 @@ GET /user "can_create_project": true, "two_factor_enabled": true, "external": false, - "private_profile": false + "private_profile": false, + "commit_email": "admin@example.com", } ``` +Users on [GitLab Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit` parameters. + ## List current user (for admins) > The `namespace_id` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82045) in GitLab 14.10. @@ -654,7 +672,8 @@ Parameters: "commit_email": "john-codes@example.com", "current_sign_in_ip": "196.165.1.102", "last_sign_in_ip": "172.127.2.22", - "namespace_id": 1 + "namespace_id": 1, + "note": null } ``` diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 4d74dbb2616..921af68d908 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -1518,6 +1518,47 @@ The voting strategy in GitLab 13.4 and later requires the primary and secondary voters to agree. ``` +#### Deprecated features + +When a feature is deprecated, add `(DEPRECATED)` to the page title or to +the heading of the section documenting the feature, immediately before +the tier badge: + +```markdown + +# Feature A (DEPRECATED) **(ALL TIERS)** + + +## Feature B (DEPRECATED) **(PREMIUM SELF)** +``` + +Add the deprecation to the version history note (you can include a link +to a replacement when available): + +```markdown +> - [Deprecated]() in GitLab 11.3. Replaced by [meaningful text](). +``` + +You can also describe the replacement in surrounding text, if available. If the +deprecation isn't obvious in existing text, you may want to include a warning: + +```markdown +WARNING: +This feature was [deprecated](link-to-issue) in GitLab 12.3 and replaced by +[Feature name](link-to-feature-documentation). +``` + +If you add `(DEPRECATED)` to the page's title and the document is linked from the docs +navigation, either remove the page from the nav or update the nav item to include the +same text before the feature name: + +```yaml + - doc_title: (DEPRECATED) Feature A +``` + +In the first major GitLab version after the feature was deprecated, be sure to +remove information about that deprecated feature. + #### End-of-life for features or products When a feature or product enters its end-of-life, indicate its status by @@ -1604,47 +1645,6 @@ To view historical information about a feature, review GitLab [release posts](https://about.gitlab.com/releases/), or search for the issue or merge request where the work was done. -### Deprecated features - -When a feature is deprecated, add `(DEPRECATED)` to the page title or to -the heading of the section documenting the feature, immediately before -the tier badge: - -```markdown - -# Feature A (DEPRECATED) **(ALL TIERS)** - - -## Feature B (DEPRECATED) **(PREMIUM SELF)** -``` - -Add the deprecation to the version history note (you can include a link -to a replacement when available): - -```markdown -> - [Deprecated]() in GitLab 11.3. Replaced by [meaningful text](). -``` - -You can also describe the replacement in surrounding text, if available. If the -deprecation isn't obvious in existing text, you may want to include a warning: - -```markdown -WARNING: -This feature was [deprecated](link-to-issue) in GitLab 12.3 and replaced by -[Feature name](link-to-feature-documentation). -``` - -If you add `(DEPRECATED)` to the page's title and the document is linked from the docs -navigation, either remove the page from the nav or update the nav item to include the -same text before the feature name: - -```yaml - - doc_title: (DEPRECATED) Feature A -``` - -In the first major GitLab version after the feature was deprecated, be sure to -remove information about that deprecated feature. - ## Products and features Refer to the information in this section when describing products and features diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md index 1a1a607e76f..b18d250f717 100644 --- a/doc/operations/incident_management/incidents.md +++ b/doc/operations/incident_management/incidents.md @@ -276,7 +276,7 @@ by changing the status. Setting the status to: For [incidents created from alerts](alerts.md#create-an-incident-from-an-alert), updating the incident status also updates the alert status. -## Change escalation policy **(PREMIUM)** +### Change escalation policy **(PREMIUM)** > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5716) in GitLab 14.9 [with a flag](../../administration/feature_flags.md) named `incident_escalations`. Disabled by default. diff --git a/doc/update/removals.md b/doc/update/removals.md index 80f5006982e..7e2b4f84fa1 100644 --- a/doc/update/removals.md +++ b/doc/update/removals.md @@ -28,12 +28,6 @@ For removal reviewers (Technical Writers only): https://about.gitlab.com/handbook/marketing/blog/release-posts/#update-the-removals-doc --> -## 15.0 - -### Request a new review - -The ability to [request a new review](https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#request-a-new-review) has been removed in GitLab 15.0. This feature is replaced with [requesting attention](https://docs.gitlab.com/ee/user/project/merge_requests/#request-attention-to-a-merge-request) to a merge request. - ## 14.9 ### Integrated error tracking disabled by default diff --git a/doc/user/admin_area/monitoring/background_migrations.md b/doc/user/admin_area/monitoring/background_migrations.md index 260a8515a1a..726827054da 100644 --- a/doc/user/admin_area/monitoring/background_migrations.md +++ b/doc/user/admin_area/monitoring/background_migrations.md @@ -185,3 +185,12 @@ The results from the query can be plugged into the command: ```shell sudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[["id"]\, ["id_convert_to_bigint"]]'] ``` + +### The `BackfillNamespaceIdForNamespaceRoute` batched migration job fails + +In GitLab 14.8, the `BackfillNamespaceIdForNamespaceRoute` batched background migration job +may fail to complete. When retried, a `500 Server Error` is returned. This issue was +[resolved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82387) in GitLab 14.9. + +To resolve this issue, [upgrade GitLab](../../../update/index.md) from 14.8 to 14.9. +You can ignore the failed batch migration until after you update to GitLab 14.9. diff --git a/doc/user/project/import/img/gitlab_import_history_page_v14_10.png b/doc/user/project/import/img/gitlab_import_history_page_v14_10.png new file mode 100644 index 00000000000..c93b5ed2b27 Binary files /dev/null and b/doc/user/project/import/img/gitlab_import_history_page_v14_10.png differ diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index 41ef15108ec..432f043f945 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -30,6 +30,30 @@ repository is too large, the import can timeout. You can also [connect your external repository to get CI/CD benefits](../../../ci/ci_cd_for_external_repos/index.md). +## Project import history + +You can view all project imports created by you. This list includes the following: + +- Source (without credentials for security reasons) +- Destination +- Status +- Error details if the import failed + +To view project import history: + +1. Sign in to GitLab. +1. On the top bar, select **New** (**{plus}**). +1. Select **New project/repository**. +1. Select **Import project**. +1. Select **History**. + +![Project import history page](img/gitlab_import_history_page_v14_10.png) + +The history also includes projects created from [built-in](../working_with_projects.md#create-a-project-from-a-built-in-template) +or [custom](../working_with_projects.md#create-a-project-from-a-built-in-template) +templates. GitLab uses [import repository by URL](repo_by_url.md) +to create a new project from a template. + ## LFS authentication When importing a project that contains LFS objects, if the project has an [`.lfsconfig`](https://github.com/git-lfs/git-lfs/blob/master/docs/man/git-lfs-config.5.ronn) diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 843f72c0e1d..8b27d8d2163 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -25,7 +25,7 @@ module API detail 'This feature was introduced in GitLab 10.6.' end get ':id/export/download' do - check_rate_limit! :project_download_export, scope: [current_user, user_project] + check_rate_limit! :project_download_export, scope: [current_user, user_project.namespace] if user_project.export_file_exists? if user_project.export_archive_exists? diff --git a/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml index 0ef6f63bb94..a3620cc9733 100644 --- a/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml @@ -31,14 +31,7 @@ secret_detection: script: - if [ -n "$CI_COMMIT_TAG" ]; then echo "Skipping Secret Detection for tags. No code changes have occurred."; exit 0; fi # Historic scan - - | - if [ "$SECRET_DETECTION_HISTORIC_SCAN" == "true" ] - then - echo "historic scan" - git fetch --unshallow origin $CI_COMMIT_REF_NAME - /analyzer run - exit - fi + - if [ "$SECRET_DETECTION_HISTORIC_SCAN" == "true" ]; then echo "Running Secret Detection Historic Scan"; /analyzer run; exit; fi # Default branch scan - if [ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]; then echo "Running Secret Detection on default branch."; /analyzer run; exit; fi # Push event diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5a4868fcc0c..f1e022aa053 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1276,9 +1276,6 @@ msgid_plural "+%d more" msgstr[0] "" msgstr[1] "" -msgid "+%{approvers} more approvers" -msgstr "" - msgid "+%{extra} more" msgstr "" @@ -1596,9 +1593,6 @@ msgstr "" msgid "A member of the abuse team will review your report as soon as possible." msgstr "" -msgid "A merge request hasn't yet been merged" -msgstr "" - msgid "A new Auto DevOps pipeline has been created, go to the Pipelines page for details" msgstr "" @@ -3666,6 +3660,12 @@ msgstr "" msgid "Allow \"%{group_name}\" to sign you in" msgstr "" +msgid "Allow access only to members of this group" +msgstr "" + +msgid "Allow access to everyone" +msgstr "" + msgid "Allow access to members of the following group" msgstr "" @@ -4514,9 +4514,6 @@ msgstr "" msgid "Applying suggestions..." msgstr "" -msgid "Approval Status" -msgstr "" - msgid "Approval rules" msgstr "" @@ -4708,15 +4705,6 @@ msgstr "" msgid "ApprovalSettings|This setting is configured in %{groupName} and can only be changed in the group settings by an administrator or group owner." msgstr "" -msgid "ApprovalStatusTooltip|Adheres to separation of duties" -msgstr "" - -msgid "ApprovalStatusTooltip|At least one rule does not adhere to separation of duties" -msgstr "" - -msgid "ApprovalStatusTooltip|Fails to adhere to separation of duties" -msgstr "" - msgid "Approvals are optional." msgstr "" @@ -9259,9 +9247,6 @@ msgstr "" msgid "Compliance report" msgstr "" -msgid "ComplianceDashboard|created by:" -msgstr "" - msgid "ComplianceFrameworks|Add framework" msgstr "" @@ -13145,6 +13130,9 @@ msgstr "" msgid "Disable group runners" msgstr "" +msgid "Disable the group-level wiki" +msgstr "" + msgid "Disable two-factor authentication" msgstr "" @@ -15155,6 +15143,9 @@ msgstr "" msgid "Expiration date:" msgstr "" +msgid "Expire access tokens" +msgstr "" + msgid "Expired" msgstr "" @@ -18550,9 +18541,6 @@ msgstr "" msgid "Helps reduce request volume for protected paths." msgstr "" -msgid "Here you will find recent merge request activity" -msgstr "" - msgid "Hi %{username}!" msgstr "" @@ -37528,9 +37516,6 @@ msgstr "" msgid "The comparison view may be inaccurate due to merge conflicts." msgstr "" -msgid "The compliance report captures merged changes that violate compliance best practices." -msgstr "" - msgid "The compliance report shows the merge request violations merged in protected environments." msgstr "" @@ -40474,9 +40459,6 @@ msgstr "" msgid "Updated date" msgstr "" -msgid "Updates" -msgstr "" - msgid "Updating" msgstr "" @@ -43960,9 +43942,6 @@ msgid_plural "approvals" msgstr[0] "" msgstr[1] "" -msgid "approved by: " -msgstr "" - msgid "archived" msgstr "" @@ -44881,9 +44860,6 @@ msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" -msgid "merged %{timeAgo}" -msgstr "" - msgid "metric_id must be unique across a project" msgstr "" @@ -45302,9 +45278,6 @@ msgstr "" msgid "new merge request" msgstr "" -msgid "no approvers" -msgstr "" - msgid "no expiration" msgstr "" diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb index 3c8a6cf6a1d..6a9249621e1 100644 --- a/qa/qa/page/component/access_tokens.rb +++ b/qa/qa/page/component/access_tokens.rb @@ -19,7 +19,7 @@ module QA end base.view 'app/views/shared/tokens/_scopes_form.html.haml' do - element :api_checkbox, '#{scope}_checkbox' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck + element :api_label, '#{scope}_label' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck end base.view 'app/views/shared/access_tokens/_created_container.html.haml' do @@ -36,7 +36,7 @@ module QA end def check_api - check_element(:api_checkbox) + click_element(:api_label) end def click_create_token_button diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb index 2075f302370..c014563671d 100644 --- a/qa/qa/resource/runner.rb +++ b/qa/qa/resource/runner.rb @@ -31,7 +31,7 @@ module QA end def fabricate_via_api! - Service::DockerRun::GitlabRunner.new(name).tap do |runner| + @docker_container = Service::DockerRun::GitlabRunner.new(name).tap do |runner| runner.pull runner.token = @token ||= project.runners_token runner.address = Runtime::Scenario.gitlab_address @@ -48,11 +48,20 @@ module QA def remove_via_api! runners = list_of_runners(tag_list: @tags) - return if runners.blank? + # If we have no runners, print the logs from the runner docker container in case they show why it isn't running. + if runners.blank? + dump_logs + + return + end this_runner = runners.find { |runner| runner[:description] == name } + # As above, but now we should have a specific runner. If not, print the logs from the runner docker container + # to see if we can find out why the runner isn't running. unless this_runner + dump_logs + raise "Project #{project.path_with_namespace} does not have a runner with a description matching #{name} #{"or tags #{@tags}" if @tags&.any?}. Runners available: #{runners}" end @@ -73,6 +82,10 @@ module QA parse_body(response) end + def reload! + super if method(:running?).super_method.call + end + def api_delete_path "/runners/#{id}" end @@ -86,6 +99,16 @@ module QA def api_post_body end + + private + + def dump_logs + if @docker_container.running? + @docker_container.logs { |line| QA::Runtime::Logger.debug(line) } + else + QA::Runtime::Logger.debug("No runner container found named #{name}") + end + end end end end diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb index 512960e8232..85c06e6c307 100644 --- a/qa/qa/service/docker_run/base.rb +++ b/qa/qa/service/docker_run/base.rb @@ -11,6 +11,12 @@ module QA @runner_network = Runtime::Scenario.attributes[:runner_network] || @network end + def logs + shell "docker logs #{@name}" do |line| + yield " #{line.chomp}" + end + end + def network shell "docker network inspect #{@network}" rescue CommandError diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb index 595d47bf162..0a8ac39dabd 100644 --- a/qa/qa/service/docker_run/gitlab_runner.rb +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -43,6 +43,8 @@ module QA #{@image} #{add_gitlab_tls_cert if @address.include? "https"} && docker exec --detach #{@name} sh -c "#{register_command}" CMD + wait_until_running_and_configured + # Prove airgappedness if runner_network == 'airgapped' shell("docker exec #{@name} sh -c '#{prove_airgap}'") @@ -111,6 +113,10 @@ module QA && docker cp #{gitlab_tls_certificate.path} #{@name}:/etc/gitlab-runner/certs/gitlab.test.crt CMD end + + def wait_until_running_and_configured + wait_until_shell_command_matches("docker logs #{@name}", /Configuration loaded/) + end end end end diff --git a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb index a59e4a99b7d..79bba484bea 100644 --- a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb +++ b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true module QA - RSpec.describe 'Manage', :github, :requires_admin do + # Spec uses real github.com, which means outage of github.com can actually block deployment + # Keep spec in reliable bucket but don't run in blocking pipelines + RSpec.describe 'Manage', :github, :reliable, :skip_live_env, :requires_admin do describe 'Project import', issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/353583' do let!(:api_client) { Runtime::API::Client.as_admin } let!(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } } diff --git a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb index 64b15c6ff74..d2eba686992 100644 --- a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb +++ b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb @@ -15,15 +15,11 @@ module QA end end - before do - sleep 5 # Runner should register within 5 seconds - end - # Removing a runner via the UI is covered by `spec/features/runners_spec.rb`` it 'removes the runner' do - runners = runner.list_of_runners(tag_list: runner_tags) - - expect(runners.size).to eq(1) + runners = nil + expect { (runners = runner.list_of_runners(tag_list: runner_tags)).size } + .to eventually_eq(1).within(max_duration: 10, sleep_interval: 1) expect(runners.first[:description]).to eq(executor) request = Runtime::API::Request.new(api_client, "runners/#{runners.first[:id]}") diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index a74eda1596b..3bf5a11b074 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true module QA - RSpec.describe 'Manage', :github, :requires_admin do + # Spec uses real github.com, which means outage of github can actually block deployment + # Keep spec in reliable bucket but don't run in blocking pipelines + RSpec.describe 'Manage', :github, :reliable, :skip_live_env, :requires_admin do describe 'Project import' do let(:github_repo) { 'gitlab-qa-github/import-test' } let(:api_client) { Runtime::API::Client.as_admin } diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index 8aa01888ae3..f8261bba342 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -22,8 +22,6 @@ module QA Page::Project::Menu.perform(&:go_to_ci_cd_settings) Page::Project::Settings::CiCd.perform do |settings| - sleep 5 # Runner should register within 5 seconds - settings.expand_runners_settings do |page| expect(page).to have_content(executor) expect(page).to have_online_runner diff --git a/qa/spec/service/docker_run/gitlab_runner_spec.rb b/qa/spec/service/docker_run/gitlab_runner_spec.rb index a8838db10cf..d9f201cf67e 100644 --- a/qa/spec/service/docker_run/gitlab_runner_spec.rb +++ b/qa/spec/service/docker_run/gitlab_runner_spec.rb @@ -24,6 +24,7 @@ module QA before do allow(subject).to receive(:shell) + allow(subject).to receive(:wait_until_running_and_configured) end context 'defaults' do diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index aba815cdf28..5a3f439009d 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -128,7 +128,7 @@ function run_locally_or_in_docker() { $cmd $args elif hash docker 2>/dev/null then - docker run -t -v ${PWD}:/gitlab -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.15-vale-2.15.0-markdownlint-0.31.0 ${cmd} ${args} + docker run -t -v ${PWD}:/gitlab -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.15-vale-2.15.5-markdownlint-0.31.1 ${cmd} ${args} else echo echo " ✖ ERROR: '${cmd}' not found. Install '${cmd}' or Docker to proceed." >&2 diff --git a/spec/experiments/ios_specific_templates_experiment_spec.rb b/spec/experiments/ios_specific_templates_experiment_spec.rb new file mode 100644 index 00000000000..4d02381dbde --- /dev/null +++ b/spec/experiments/ios_specific_templates_experiment_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IosSpecificTemplatesExperiment do + subject do + described_class.new(actor: user, project: project) do |e| + e.candidate { true } + end.run + end + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :auto_devops_disabled) } + + let!(:project_setting) { create(:project_setting, project: project, target_platforms: target_platforms) } + let(:target_platforms) { %w(ios) } + + before do + stub_experiments(ios_specific_templates: :candidate) + project.add_developer(user) if user + end + + it { is_expected.to be true } + + describe 'skipping the experiment' do + context 'no actor' do + let_it_be(:user) { nil } + + it { is_expected.to be_falsey } + end + + context 'actor cannot create pipelines' do + before do + project.add_guest(user) + end + + it { is_expected.to be_falsey } + end + + context 'targeting a non iOS platform' do + let(:target_platforms) { [] } + + it { is_expected.to be_falsey } + end + + context 'project has a ci.yaml file' do + before do + allow(project).to receive(:has_ci?).and_return(true) + end + + it { is_expected.to be_falsey } + end + + context 'project has pipelines' do + before do + create(:ci_pipeline, project: project) + end + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index 6643ebe82e6..15bc2318022 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -36,14 +36,14 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js do click_on "1" # Scopes - check "api" + check "read_api" check "read_user" click_on "Create impersonation token" expect(active_impersonation_tokens).to have_text(name) expect(active_impersonation_tokens).to have_text('in') - expect(active_impersonation_tokens).to have_text('api') + expect(active_impersonation_tokens).to have_text('read_api') expect(active_impersonation_tokens).to have_text('read_user') expect(PersonalAccessTokensFinder.new(impersonation: true).execute.count).to equal(1) expect(created_impersonation_token).not_to be_empty diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index f1e5658cd7b..8cbc0491441 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -47,14 +47,14 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do click_on "1" # Scopes - check "api" + check "read_api" check "read_user" click_on "Create personal access token" expect(active_personal_access_tokens).to have_text(name) expect(active_personal_access_tokens).to have_text('in') - expect(active_personal_access_tokens).to have_text('api') + expect(active_personal_access_tokens).to have_text('read_api') expect(active_personal_access_tokens).to have_text('read_user') expect(created_personal_access_token).not_to be_empty end diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb index bf40aea4a5d..271dce44db7 100644 --- a/spec/finders/users_finder_spec.rb +++ b/spec/finders/users_finder_spec.rb @@ -14,7 +14,7 @@ RSpec.describe UsersFinder do it 'returns searchable users' do users = described_class.new(user).execute - expect(users).to contain_exactly(user, normal_user, external_user, omniauth_user, internal_user, admin_user, project_bot) + expect(users).to contain_exactly(user, normal_user, external_user, unconfirmed_user, omniauth_user, internal_user, admin_user, project_bot) end it 'filters by username' do @@ -56,7 +56,7 @@ RSpec.describe UsersFinder do it 'filters by non external users' do users = described_class.new(user, non_external: true).execute - expect(users).to contain_exactly(user, normal_user, omniauth_user, internal_user, admin_user, project_bot) + expect(users).to contain_exactly(user, normal_user, unconfirmed_user, omniauth_user, internal_user, admin_user, project_bot) end it 'filters by created_at' do @@ -73,7 +73,7 @@ RSpec.describe UsersFinder do it 'filters by non internal users' do users = described_class.new(user, non_internal: true).execute - expect(users).to contain_exactly(user, normal_user, external_user, omniauth_user, admin_user, project_bot) + expect(users).to contain_exactly(user, normal_user, unconfirmed_user, external_user, omniauth_user, admin_user, project_bot) end it 'does not filter by custom attributes' do @@ -82,18 +82,18 @@ RSpec.describe UsersFinder do custom_attributes: { foo: 'bar' } ).execute - expect(users).to contain_exactly(user, normal_user, external_user, omniauth_user, internal_user, admin_user, project_bot) + expect(users).to contain_exactly(user, normal_user, external_user, unconfirmed_user, omniauth_user, internal_user, admin_user, project_bot) end it 'orders returned results' do users = described_class.new(user, sort: 'id_asc').execute - expect(users).to eq([normal_user, admin_user, external_user, omniauth_user, internal_user, project_bot, user]) + expect(users).to eq([normal_user, admin_user, external_user, unconfirmed_user, omniauth_user, internal_user, project_bot, user]) end it 'does not filter by admins' do users = described_class.new(user, admins: true).execute - expect(users).to contain_exactly(user, normal_user, external_user, admin_user, omniauth_user, internal_user, project_bot) + expect(users).to contain_exactly(user, normal_user, external_user, admin_user, unconfirmed_user, omniauth_user, internal_user, project_bot) end end diff --git a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js index b6be53fec99..7952661e2d2 100644 --- a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js +++ b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js @@ -8,6 +8,9 @@ import { LARGE_AVATAR_PX, PROJECTS_CATEGORY, SMALL_AVATAR_PX, + ISSUES_CATEGORY, + MERGE_REQUEST_CATEGORY, + RECENT_EPICS_CATEGORY, } from '~/header_search/constants'; import { MOCK_GROUPED_AUTOCOMPLETE_OPTIONS, @@ -50,7 +53,12 @@ describe('HeaderSearchAutocompleteItems', () => { const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findGlDropdownDividers = () => wrapper.findAllComponents(GlDropdownDivider); const findFirstDropdownItem = () => findDropdownItems().at(0); - const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text()); + const findDropdownItemTitles = () => + findDropdownItems().wrappers.map((w) => w.findAll('span').at(1).text()); + const findDropdownItemSubTitles = () => + findDropdownItems() + .wrappers.filter((w) => w.findAll('span').length > 2) + .map((w) => w.findAll('span').at(2).text()); const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href')); const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findGlAvatar = () => wrapper.findComponent(GlAvatar); @@ -95,10 +103,17 @@ describe('HeaderSearchAutocompleteItems', () => { }); it('renders titles correctly', () => { - const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.label); + const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.value || o.label); expect(findDropdownItemTitles()).toStrictEqual(expectedTitles); }); + it('renders sub-titles correctly', () => { + const expectedSubTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.filter((o) => o.value).map( + (o) => o.label, + ); + expect(findDropdownItemSubTitles()).toStrictEqual(expectedSubTitles); + }); + it('renders links correctly', () => { const expectedLinks = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.url); expect(findDropdownItemLinks()).toStrictEqual(expectedLinks); @@ -106,15 +121,30 @@ describe('HeaderSearchAutocompleteItems', () => { }); describe.each` - item | showAvatar | avatarSize - ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} - ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: '/123' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} - ${{ data: [{ category: 'Help', avatar_url: '' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} - ${{ data: [{ category: 'Settings' }] }} | ${false} | ${false} - `('GlAvatar', ({ item, showAvatar, avatarSize }) => { + item | showAvatar | avatarSize | searchContext | entityId | entityName + ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 29 } }} | ${'29'} | ${''} + ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: '/123' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 12 } }} | ${'12'} | ${''} + ${{ data: [{ category: 'Help', avatar_url: '' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'0'} | ${''} + ${{ data: [{ category: 'Settings' }] }} | ${false} | ${false} | ${null} | ${false} | ${false} + ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'1'} | ${'test1'} + ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'2'} | ${'test2'} + ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'3'} | ${'test3'} + ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'4'} | ${'test4'} + ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'5'} | ${'test5'} + ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 6, group_name: 'test6' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'6'} | ${'test6'} + ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 7, project_name: 'test7' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'7'} | ${'test7'} + ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 8, project_name: 'test8' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'8'} | ${'test8'} + ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 9, project_name: 'test9' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'9'} | ${'test9'} + ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 10, group_name: 'test10' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'10'} | ${'test10'} + ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 11, group_name: 'test11' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'11'} | ${'test11'} + ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 12, project_name: 'test12' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'12'} | ${'test12'} + ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 13, project_name: 'test13' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'13'} | ${'test13'} + ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 14, project_name: 'test14' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'14'} | ${'test14'} + ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 15, group_name: 'test15' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'15'} | ${'test15'} + `('GlAvatar', ({ item, showAvatar, avatarSize, searchContext, entityId, entityName }) => { describe(`when category is ${item.data[0].category} and avatar_url is ${item.data[0].avatar_url}`, () => { beforeEach(() => { - createComponent({}, { autocompleteGroupedSearchOptions: () => [item] }); + createComponent({ searchContext }, { autocompleteGroupedSearchOptions: () => [item] }); }); it(`should${showAvatar ? '' : ' not'} render`, () => { @@ -124,6 +154,16 @@ describe('HeaderSearchAutocompleteItems', () => { it(`should set avatarSize to ${avatarSize}`, () => { expect(findGlAvatar().exists() && findGlAvatar().attributes('size')).toBe(avatarSize); }); + + it(`should set avatar entityId to ${entityId}`, () => { + expect(findGlAvatar().exists() && findGlAvatar().attributes('entityid')).toBe(entityId); + }); + + it(`should set avatar entityName to ${entityName}`, () => { + expect(findGlAvatar().exists() && findGlAvatar().attributes('entityname')).toBe( + entityName, + ); + }); }); }); }); diff --git a/spec/frontend/header_search/mock_data.js b/spec/frontend/header_search/mock_data.js index 358c224dfa6..b6f0fdcc29d 100644 --- a/spec/frontend/header_search/mock_data.js +++ b/spec/frontend/header_search/mock_data.js @@ -96,19 +96,22 @@ export const MOCK_AUTOCOMPLETE_OPTIONS_RES = [ { category: 'Projects', id: 1, - label: 'MockProject1', + label: 'Gitlab Org / MockProject1', + value: 'MockProject1', url: 'project/1', }, { category: 'Groups', id: 1, - label: 'MockGroup1', + label: 'Gitlab Org / MockGroup1', + value: 'MockGroup1', url: 'group/1', }, { category: 'Projects', id: 2, - label: 'MockProject2', + label: 'Gitlab Org / MockProject2', + value: 'MockProject2', url: 'project/2', }, { @@ -123,21 +126,24 @@ export const MOCK_AUTOCOMPLETE_OPTIONS = [ category: 'Projects', html_id: 'autocomplete-Projects-0', id: 1, - label: 'MockProject1', + label: 'Gitlab Org / MockProject1', + value: 'MockProject1', url: 'project/1', }, { category: 'Groups', html_id: 'autocomplete-Groups-1', id: 1, - label: 'MockGroup1', + label: 'Gitlab Org / MockGroup1', + value: 'MockGroup1', url: 'group/1', }, { category: 'Projects', html_id: 'autocomplete-Projects-2', id: 2, - label: 'MockProject2', + label: 'Gitlab Org / MockProject2', + value: 'MockProject2', url: 'project/2', }, { @@ -157,7 +163,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ html_id: 'autocomplete-Projects-0', id: 1, - label: 'MockProject1', + label: 'Gitlab Org / MockProject1', + value: 'MockProject1', url: 'project/1', }, { @@ -165,7 +172,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ html_id: 'autocomplete-Projects-2', id: 2, - label: 'MockProject2', + label: 'Gitlab Org / MockProject2', + value: 'MockProject2', url: 'project/2', }, ], @@ -178,7 +186,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ html_id: 'autocomplete-Groups-1', id: 1, - label: 'MockGroup1', + label: 'Gitlab Org / MockGroup1', + value: 'MockGroup1', url: 'group/1', }, ], @@ -202,21 +211,24 @@ export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [ category: 'Projects', html_id: 'autocomplete-Projects-0', id: 1, - label: 'MockProject1', + label: 'Gitlab Org / MockProject1', + value: 'MockProject1', url: 'project/1', }, { category: 'Projects', html_id: 'autocomplete-Projects-2', id: 2, - label: 'MockProject2', + label: 'Gitlab Org / MockProject2', + value: 'MockProject2', url: 'project/2', }, { category: 'Groups', html_id: 'autocomplete-Groups-1', id: 1, - label: 'MockGroup1', + label: 'Gitlab Org / MockGroup1', + value: 'MockGroup1', url: 'group/1', }, { diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js index b0d5859cd31..3d7bf7acb41 100644 --- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js +++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js @@ -72,7 +72,7 @@ describe('GroupsListItem', () => { expect(addSubscriptionSpy).toHaveBeenCalledWith(mockSubscriptionPath, mockGroup1.full_path); expect(persistAlert).toHaveBeenCalledWith({ - linkUrl: '/help/integration/jira_development_panel.html#usage', + linkUrl: '/help/integration/jira_development_panel.html#use-the-integration', message: 'You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}', title: 'Namespace successfully linked', diff --git a/spec/helpers/ci/pipelines_helper_spec.rb b/spec/helpers/ci/pipelines_helper_spec.rb index 2b76eaa87bc..c473e1e4ab6 100644 --- a/spec/helpers/ci/pipelines_helper_spec.rb +++ b/spec/helpers/ci/pipelines_helper_spec.rb @@ -151,5 +151,46 @@ RSpec.describe Ci::PipelinesHelper do end end end + + describe 'the `registration_token` attribute' do + subject { data[:registration_token] } + + describe 'when the project is eligible for the `ios_specific_templates` experiment' do + let_it_be(:project) { create(:project, :auto_devops_disabled) } + let_it_be(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + project.add_developer(user) + create(:project_setting, project: project, target_platforms: %w(ios)) + end + + context 'when the `ios_specific_templates` experiment variant is control' do + before do + stub_experiments(ios_specific_templates: :control) + end + + it { is_expected.to be_nil } + end + + context 'when the `ios_specific_templates` experiment variant is candidate' do + before do + stub_experiments(ios_specific_templates: :candidate) + end + + context 'when the user cannot register project runners' do + before do + allow(helper).to receive(:can?).with(user, :register_project_runners, project).and_return(false) + end + + it { is_expected.to be_nil } + end + + context 'when the user can register project runners' do + it { is_expected.to eq(project.runners_token) } + end + end + end + end end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 96d8157dfde..d1be451a759 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -71,7 +71,7 @@ RSpec.describe SearchHelper do create(:group).add_owner(user) result = search_autocomplete_opts("gro").first - expect(result.keys).to match_array(%i[category id label url avatar_url]) + expect(result.keys).to match_array(%i[category id value label url avatar_url]) end it 'includes the users recently viewed issues', :aggregate_failures do diff --git a/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb b/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb new file mode 100644 index 00000000000..fbd5fe546fa --- /dev/null +++ b/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true +require 'spec_helper' +require_migration! + +RSpec.describe UpdatePagesOnboardingState do + let(:migration) { described_class.new } + let!(:namespaces) { table(:namespaces) } + let!(:projects) { table(:projects) } + let!(:project_pages_metadata) { table(:project_pages_metadata) } + + let!(:namespace1) { namespaces.create!(name: 'foo', path: 'foo') } + let!(:namespace2) { namespaces.create!(name: 'bar', path: 'bar') } + let!(:project1) { projects.create!(namespace_id: namespace1.id) } + let!(:project2) { projects.create!(namespace_id: namespace2.id) } + let!(:pages_metadata1) do + project_pages_metadata.create!( + project_id: project1.id, + deployed: true, + onboarding_complete: false + ) + end + + let!(:pages_metadata2) do + project_pages_metadata.create!( + project_id: project2.id, + deployed: false, + onboarding_complete: false + ) + end + + describe '#up' do + before do + migration.up + end + + it 'sets the onboarding_complete attribute to the value of deployed' do + expect(pages_metadata1.reload.onboarding_complete).to eq(true) + expect(pages_metadata2.reload.onboarding_complete).to eq(false) + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'sets all onboarding_complete attributes to false' do + expect(pages_metadata1.reload.onboarding_complete).to eq(false) + expect(pages_metadata2.reload.onboarding_complete).to eq(false) + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ead7f7d0786..43088739f34 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2293,6 +2293,44 @@ RSpec.describe Project, factory_default: :keep do end end + describe '#pages_show_onboarding?' do + let(:project) { create(:project) } + + subject { project.pages_show_onboarding? } + + context "if there is no metadata" do + it { is_expected.to be_truthy } + end + + context 'if onboarding is complete' do + before do + project.pages_metadatum.update_column(:onboarding_complete, true) + end + + it { is_expected.to be_falsey } + end + + context 'if there is metadata, but onboarding is not complete' do + before do + project.pages_metadatum.update_column(:onboarding_complete, false) + end + + it { is_expected.to be_truthy } + end + + # During migration, the onboarding_complete property can still be false, + # but will be updated later. To account for that case, pages_show_onboarding? + # should return false if `deployed` is true. + context "will return false if pages is deployed even if onboarding_complete is false" do + before do + project.pages_metadatum.update_column(:onboarding_complete, false) + project.pages_metadatum.update_column(:deployed, true) + end + + it { is_expected.to be_falsey } + end + end + describe '#pages_deployed?' do let(:project) { create(:project) } @@ -6626,6 +6664,25 @@ RSpec.describe Project, factory_default: :keep do end end + describe '#mark_pages_onboarding_complete' do + let(:project) { create(:project) } + + it "creates new record and sets onboarding_complete to true if none exists yet" do + project.mark_pages_onboarding_complete + + expect(project.pages_metadatum.reload.onboarding_complete).to eq(true) + end + + it "overrides an existing setting" do + pages_metadatum = project.pages_metadatum + pages_metadatum.update!(onboarding_complete: false) + + expect do + project.mark_pages_onboarding_complete + end.to change { pages_metadatum.reload.onboarding_complete }.from(false).to(true) + end + end + describe '#mark_pages_as_deployed' do let(:project) { create(:project) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5f2777b12d2..09e3a9c1156 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4988,17 +4988,36 @@ RSpec.describe User do end describe '#attention_requested_open_merge_requests_count' do - it 'returns number of open merge requests from non-archived projects' do - user = create(:user) - project = create(:project, :public) - archived_project = create(:project, :public, :archived) + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:archived_project) { create(:project, :public, :archived) } + before do create(:merge_request, source_project: project, author: user, reviewers: [user]) create(:merge_request, :closed, source_project: project, author: user, reviewers: [user]) create(:merge_request, source_project: archived_project, author: user, reviewers: [user]) + end + it 'returns number of open merge requests from non-archived projects' do + expect(Rails.cache).not_to receive(:fetch) expect(user.attention_requested_open_merge_requests_count(force: true)).to eq 1 end + + context 'when uncached_mr_attention_requests_count is disabled' do + before do + stub_feature_flags(uncached_mr_attention_requests_count: false) + end + + it 'fetches from cache' do + expect(Rails.cache).to receive(:fetch).with( + user.attention_request_cache_key, + force: false, + expires_in: described_class::COUNT_CACHE_VALIDITY_PERIOD + ).and_call_original + + expect(user.attention_requested_open_merge_requests_count).to eq 1 + end + end end describe '#assigned_open_issues_count' do @@ -6748,9 +6767,9 @@ RSpec.describe User do let_it_be(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } let_it_be(:internal_user) { User.alert_bot.tap { |u| u.confirm } } - it 'does not return blocked, banned or unconfirmed users' do + it 'does not return blocked or banned users' do expect(described_class.without_forbidden_states).to match_array([ - normal_user, admin_user, external_user, omniauth_user, internal_user + normal_user, admin_user, external_user, unconfirmed_user, omniauth_user, internal_user ]) end end diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index 2bc31153f2c..07efd56fef4 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -260,6 +260,29 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.') end end + + context 'applies correct scope when throttling' do + before do + stub_application_setting(project_download_export_limit: 1) + end + + it 'throttles downloads within same namespaces' do + # simulate prior request to the same namespace, which increments the rate limit counter for that scope + Gitlab::ApplicationRateLimiter.throttled?(:project_download_export, scope: [user, project_finished.namespace]) + + get api(download_path_finished, user) + expect(response).to have_gitlab_http_status(:too_many_requests) + end + + it 'allows downloads from different namespaces' do + # simulate prior request to a different namespace, which increments the rate limit counter for that scope + Gitlab::ApplicationRateLimiter.throttled?(:project_download_export, + scope: [user, create(:project, :with_export).namespace]) + + get api(download_path_finished, user) + expect(response).to have_gitlab_http_status(:ok) + end + end end context 'when user is a maintainer' do diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb index ae246a87bb6..215d9d3e5a8 100644 --- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb +++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb @@ -29,15 +29,15 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type| click_on '1' # Scopes - check 'api' check 'read_api' + check 'read_repository' click_on "Create #{resource_type} access token" expect(active_resource_access_tokens).to have_text(name) expect(active_resource_access_tokens).to have_text('in') - expect(active_resource_access_tokens).to have_text('api') expect(active_resource_access_tokens).to have_text('read_api') + expect(active_resource_access_tokens).to have_text('read_repository') expect(active_resource_access_tokens).to have_text('Maintainer') expect(created_resource_access_token).not_to be_empty end