diff options
74 files changed, 632 insertions, 636 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index f1bd173ff6d..d3069657e88 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -38,7 +38,7 @@ review-build-cng: - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng # When the job is manual, review-deploy is also manual and we don't want people # to have to manually start the jobs in sequence, so we do it for them. - - '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-deploy"' + - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"' .review-workflow-base: extends: @@ -78,8 +78,8 @@ review-deploy: - disable_sign_ups || (delete_release && exit 1) # When the job is manual, review-qa-smoke is also manual and we don't want people # to have to manually start the jobs in sequence, so we do it for them. - - '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-qa-smoke"' - - '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-performance"' + - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"' + - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"' after_script: # Run seed-dast-test-data.sh only when DAST_RUN is set to true. This is to pupulate review app with data for DAST scan. # Set DAST_RUN to true when jobs are manually scheduled. diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml index aec0a1640f1..e4b7047ef71 100644 --- a/.gitlab/ci/test-metadata.gitlab-ci.yml +++ b/.gitlab/ci/test-metadata.gitlab-ci.yml @@ -1,5 +1,6 @@ .tests-metadata-state: - image: ruby:2.7 + variables: + TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache" before_script: - source scripts/utils.sh artifacts: @@ -16,8 +17,7 @@ retrieve-tests-metadata: - .test-metadata:rules:retrieve-tests-metadata stage: prepare script: - - install_gitlab_gem - - source ./scripts/rspec_helpers.sh + - source scripts/rspec_helpers.sh - retrieve_tests_metadata update-tests-metadata: diff --git a/app/assets/javascripts/clone_panel.js b/app/assets/javascripts/clone_panel.js new file mode 100644 index 00000000000..362e6c5c5ce --- /dev/null +++ b/app/assets/javascripts/clone_panel.js @@ -0,0 +1,42 @@ +import $ from 'jquery'; + +export default function initClonePanel() { + const $cloneOptions = $('ul.clone-options-dropdown'); + if ($cloneOptions.length) { + const $cloneUrlField = $('#clone_url'); + const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label'); + const mobileCloneField = document.querySelector( + '.js-mobile-git-clone .js-clone-dropdown-label', + ); + + const selectedCloneOption = $cloneBtnLabel.text().trim(); + if (selectedCloneOption.length > 0) { + $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active'); + } + + $('a', $cloneOptions).on('click', e => { + e.preventDefault(); + const $this = $(e.currentTarget); + const url = $this.attr('href'); + const cloneType = $this.data('cloneType'); + + $('.is-active', $cloneOptions).removeClass('is-active'); + $(`a[data-clone-type="${cloneType}"]`).each(function switchProtocol() { + const $el = $(this); + const activeText = $el.find('.dropdown-menu-inner-title').text(); + const $container = $el.closest('.js-git-clone-holder, .js-mobile-git-clone'); + const $label = $container.find('.js-clone-dropdown-label'); + + $el.toggleClass('is-active'); + $label.text(activeText); + }); + + if (mobileCloneField) { + mobileCloneField.dataset.clipboardText = url; + } else { + $cloneUrlField.val(url); + } + $('.js-git-empty .js-clone').text(url); + }); + } +} diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 5317093c4cf..8c7aa04a0b6 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -9,47 +9,11 @@ import axios from '~/lib/utils/axios_utils'; import { deprecatedCreateFlash as flash } from '~/flash'; import projectSelect from '../../project_select'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; +import initClonePanel from '~/clone_panel'; export default class Project { constructor() { - const $cloneOptions = $('ul.clone-options-dropdown'); - if ($cloneOptions.length) { - const $projectCloneField = $('#project_clone'); - const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label'); - const mobileCloneField = document.querySelector( - '.js-mobile-git-clone .js-clone-dropdown-label', - ); - - const selectedCloneOption = $cloneBtnLabel.text().trim(); - if (selectedCloneOption.length > 0) { - $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active'); - } - - $('a', $cloneOptions).on('click', e => { - e.preventDefault(); - const $this = $(e.currentTarget); - const url = $this.attr('href'); - const cloneType = $this.data('cloneType'); - - $('.is-active', $cloneOptions).removeClass('is-active'); - $(`a[data-clone-type="${cloneType}"]`).each(function() { - const $el = $(this); - const activeText = $el.find('.dropdown-menu-inner-title').text(); - const $container = $el.closest('.project-clone-holder'); - const $label = $container.find('.js-clone-dropdown-label'); - - $el.toggleClass('is-active'); - $label.text(activeText); - }); - - if (mobileCloneField) { - mobileCloneField.dataset.clipboardText = url; - } else { - $projectCloneField.val(url); - } - $('.js-git-empty .js-clone').text(url); - }); - } + initClonePanel(); // Ref switcher if (document.querySelector('.js-project-refs-dropdown')) { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue index ff0d065c71d..1c9909e7178 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue @@ -1,10 +1,11 @@ <script> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { GlIcon, GlTooltipDirective, GlFormCheckbox } from '@gitlab/ui'; import { SQUASH_BEFORE_MERGE } from '../../i18n'; export default { components: { GlIcon, + GlFormCheckbox, }, directives: { GlTooltip: GlTooltipDirective, @@ -32,32 +33,23 @@ export default { tooltipTitle() { return this.isDisabled ? this.$options.i18n.tooltipTitle : null; }, - tooltipFocusable() { - return this.isDisabled ? '0' : null; - }, }, }; </script> <template> - <div class="inline"> - <label + <div class="gl-display-flex gl-align-items-center"> + <gl-form-checkbox v-gl-tooltip - :class="{ 'gl-text-gray-400': isDisabled }" - :tabindex="tooltipFocusable" - data-testid="squashLabel" + :checked="value" + :disabled="isDisabled" + name="squash" + class="qa-squash-checkbox js-squash-checkbox gl-mb-0 gl-mr-2" :title="tooltipTitle" + @change="checked => $emit('input', checked)" > - <input - :checked="value" - :disabled="isDisabled" - type="checkbox" - name="squash" - class="qa-squash-checkbox js-squash-checkbox" - @change="$emit('input', $event.target.checked)" - /> {{ $options.i18n.checkboxLabel }} - </label> + </gl-form-checkbox> <a v-if="helpPath" v-gl-tooltip diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 512649b3008..9a43a4a3a15 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -49,12 +49,12 @@ module ApplicationSettingsHelper all_protocols_enabled? || Gitlab::CurrentSettings.enabled_git_access_protocol == 'http' end - def enabled_project_button(project, protocol) + def enabled_protocol_button(container, protocol) case protocol when 'ssh' - ssh_clone_button(project, append_link: false) + ssh_clone_button(container, append_link: false) else - http_clone_button(project, append_link: false) + http_clone_button(container, append_link: false) end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index c999d1f94ad..ea24f469ffa 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -58,10 +58,10 @@ module ButtonHelper end end - def http_clone_button(project, append_link: true) + def http_clone_button(container, append_link: true) protocol = gitlab_config.protocol.upcase dropdown_description = http_dropdown_description(protocol) - append_url = project.http_url_to_repo if append_link + append_url = container.http_url_to_repo if append_link dropdown_item_with_description(protocol, dropdown_description, href: append_url, data: { clone_type: 'http' }) end @@ -74,13 +74,13 @@ module ButtonHelper end end - def ssh_clone_button(project, append_link: true) + def ssh_clone_button(container, append_link: true) if Gitlab::CurrentSettings.user_show_add_ssh_key_message? && current_user.try(:require_ssh_key?) - dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") + dropdown_description = s_("MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile") end - append_url = project.ssh_url_to_repo if append_link + append_url = container.ssh_url_to_repo if append_link dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { clone_type: 'ssh' }) end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index de1e0e4e05e..2d47ee89d11 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -31,7 +31,7 @@ module SearchHelper [ resources_results, generic_results - ].flatten.uniq do |item| + ].flatten do |item| item[:label] end end diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml index cf58cff7445..938dfc69500 100644 --- a/app/views/projects/buttons/_clone.html.haml +++ b/app/views/projects/buttons/_clone.html.haml @@ -2,7 +2,7 @@ - dropdown_class = local_assigns.fetch(:dropdown_class, '') .git-clone-holder.js-git-clone-holder - %a#clone-dropdown.gl-button.btn.btn-primary.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } + %a#clone-dropdown.gl-button.btn.btn-info.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } %span.gl-mr-2.js-clone-dropdown-label = _('Clone') = sprite_icon("chevron-down", css_class: "icon") @@ -12,7 +12,7 @@ %label.label-bold = _('Clone with SSH') .input-group - = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control qa-ssh-clone-url", readonly: true, aria: { label: 'Project clone URL' } + = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control qa-ssh-clone-url", readonly: true, aria: { label: _('Repository clone URL') } .input-group-append = clipboard_button(target: '#ssh_project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard") = render_if_exists 'projects/buttons/geo' @@ -21,7 +21,7 @@ %label.label-bold = _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase } .input-group - = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control qa-http-clone-url", readonly: true, aria: { label: 'Project clone URL' } + = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control qa-http-clone-url", readonly: true, aria: { label: _('Repository clone URL') } .input-group-append = clipboard_button(target: '#http_project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard") = render_if_exists 'projects/buttons/geo' diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index d9ad171a6cc..3ce85fb46d5 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,67 +1,66 @@ -# DANGER: Any changes to this file need to be reflected in issuables_list/components/issuable.vue! %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id, qa_selector: 'issue_container', qa_issue_title: issue.title } } - .issue-box + .issuable-info-container - if @can_bulk_update .issue-check.hidden = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected-issuable" - .issuable-info-container - .issuable-main-info - .issue-title.title - %span.issue-title-text.js-onboarding-issue-item{ dir: "auto" } - - if issue.confidential? - %span.has-tooltip{ title: _('Confidential') } - = confidential_icon(issue) - = link_to issue.title, issue_path(issue) - = render_if_exists 'projects/issues/subepic_flag', issue: issue - - if issue.tasks? - %span.task-status.d-none.d-sm-inline-block - - = issue.task_status + .issuable-main-info + .issue-title.title + %span.issue-title-text.js-onboarding-issue-item{ dir: "auto" } + - if issue.confidential? + %span.has-tooltip{ title: _('Confidential') } + = confidential_icon(issue) + = link_to issue.title, issue_path(issue) + = render_if_exists 'projects/issues/subepic_flag', issue: issue + - if issue.tasks? + %span.task-status.d-none.d-sm-inline-block + + = issue.task_status - .issuable-info - %span.issuable-reference - #{issuable_reference(issue)} - %span.issuable-authored.d-none.d-sm-inline-block - · - opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} - by #{link_to_member(@project, issue.author, avatar: false)} - = render_if_exists 'shared/issuable/gitlab_team_member_badge', {author: issue.author} - - if issue.milestone - %span.issuable-milestone.d-none.d-sm-inline-block - - = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 'true', toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do - = sprite_icon('clock', css_class: 'gl-vertical-align-text-bottom') - = issue.milestone.title - - if issue.due_date - %span.issuable-due-date.d-none.d-sm-inline-block.has-tooltip{ class: "#{'cred' if issue.overdue?}", title: _('Due date') } - - = sprite_icon('calendar') - = issue.due_date.to_s(:medium) + .issuable-info + %span.issuable-reference + #{issuable_reference(issue)} + %span.issuable-authored.d-none.d-sm-inline-block + · + opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} + by #{link_to_member(@project, issue.author, avatar: false)} + = render_if_exists 'shared/issuable/gitlab_team_member_badge', {author: issue.author} + - if issue.milestone + %span.issuable-milestone.d-none.d-sm-inline-block + + = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 'true', toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do + = sprite_icon('clock', css_class: 'gl-vertical-align-text-bottom') + = issue.milestone.title + - if issue.due_date + %span.issuable-due-date.d-none.d-sm-inline-block.has-tooltip{ class: "#{'cred' if issue.overdue?}", title: _('Due date') } + + = sprite_icon('calendar') + = issue.due_date.to_s(:medium) - = render_if_exists "projects/issues/issue_weight", issue: issue - = render_if_exists "projects/issues/health_status", issue: issue + = render_if_exists "projects/issues/issue_weight", issue: issue + = render_if_exists "projects/issues/health_status", issue: issue - - if issue.labels.any? - - - presented_labels_sorted_by_title(issue.labels, issue.project).each do |label| - = link_to_label(label, small: true) + - if issue.labels.any? + + - presented_labels_sorted_by_title(issue.labels, issue.project).each do |label| + = link_to_label(label, small: true) - = render "projects/issues/issue_estimate", issue: issue + = render "projects/issues/issue_estimate", issue: issue - .issuable-meta - %ul.controls - - if issue.closed? && issue.moved? - %li.issuable-status - = _('CLOSED (MOVED)') - - elsif issue.closed? - %li.issuable-status - = _('CLOSED') - - if issue.assignees.any? - %li.gl-display-flex - = render 'shared/issuable/assignees', project: @project, issuable: issue + .issuable-meta + %ul.controls + - if issue.closed? && issue.moved? + %li.issuable-status + = _('CLOSED (MOVED)') + - elsif issue.closed? + %li.issuable-status + = _('CLOSED') + - if issue.assignees.any? + %li.gl-display-flex + = render 'shared/issuable/assignees', project: @project, issuable: issue - = render 'shared/issuable_meta_data', issuable: issue + = render 'shared/issuable_meta_data', issuable: issue - .float-right.issuable-updated-at.d-none.d-sm-inline-block - %span - = _('updated %{time_ago}').html_safe % { time_ago: time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago') } + .float-right.issuable-updated-at.d-none.d-sm-inline-block + %span + = _('updated %{time_ago}').html_safe % { time_ago: time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago') } diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index c166642bae2..2542860c742 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -11,7 +11,7 @@ %strong= @wiki.full_path .pt-3.pt-lg-0.w-100 - = render "shared/clone_panel", project: @wiki + = render "shared/clone_panel", container: @wiki .wiki-git-access %h3= s_("WikiClone|Install Gollum") diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 9ec8d3c18cd..1ed37c7a5c4 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,11 +1,9 @@ -- project = project || @project - .git-clone-holder.js-git-clone-holder.input-group .input-group-prepend - if allowed_protocols_present? .input-group-text.clone-dropdown-btn.btn %span.js-clone-dropdown-label - = enabled_project_button(project, enabled_protocol) + = enabled_protocol_button(container, enabled_protocol) - else %a#clone-dropdown.input-group-text.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } %span.js-clone-dropdown-label @@ -13,12 +11,12 @@ = icon('caret-down') %ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown %li - = ssh_clone_button(project) + = ssh_clone_button(container) %li - = http_clone_button(project) - = render_if_exists 'shared/kerberos_clone_button', project: project + = http_clone_button(container) + = render_if_exists 'shared/kerberos_clone_button', container: container - = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: _('Project clone URL') } + = text_field_tag :clone_url, default_url_to_repo(container), class: "js-select-on-focus form-control", readonly: true, aria: { label: _('Repository clone URL') } .input-group-append - = clipboard_button(target: '#project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard") + = clipboard_button(target: '#clone_url', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard") diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml index 76ae63ca5e8..9c1e5a49b44 100644 --- a/app/views/shared/_no_password.html.haml +++ b/app/views/shared/_no_password.html.haml @@ -5,7 +5,7 @@ = sprite_icon('close', size: 16, css_class: 'gl-icon') .gl-alert-body - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password } - - set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params + - set_password_message = _("You won't be able to pull or push repositories via %{protocol} until you %{set_password_link} on your account") % translation_params = set_password_message.html_safe .gl-alert-actions = link_to _('Remind later'), '#', class: 'hide-no-password-message btn gl-alert-action btn-info btn-md gl-button' diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index a083a772233..0a7fa2a3c1e 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -4,7 +4,7 @@ %button{ class: 'gl-alert-dismiss hide-no-ssh-message', type: 'button', 'aria-label': _('Dismiss') } = sprite_icon('close', css_class: 'gl-icon s16') .gl-alert-body - = s_("MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile").html_safe + = s_("MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile") .gl-alert-actions = link_to s_('MissingSSHKeyWarningLink|Add SSH key'), profile_keys_path, class: "btn gl-alert-action btn-warning btn-md new-gl-button" = link_to s_("MissingSSHKeyWarningLink|Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, role: 'button', class: 'btn gl-alert-action btn-md btn-warning gl-button btn-warning-secondary' diff --git a/changelogs/unreleased/207869-fix-wiki-clone-panel.yml b/changelogs/unreleased/207869-fix-wiki-clone-panel.yml new file mode 100644 index 00000000000..8e09f0f9336 --- /dev/null +++ b/changelogs/unreleased/207869-fix-wiki-clone-panel.yml @@ -0,0 +1,5 @@ +--- +title: Fix repository clone panel for wikis +merge_request: 47676 +author: +type: fixed diff --git a/changelogs/unreleased/225275-add-glcheckbox-to-squah-commits.yml b/changelogs/unreleased/225275-add-glcheckbox-to-squah-commits.yml new file mode 100644 index 00000000000..99ad834046d --- /dev/null +++ b/changelogs/unreleased/225275-add-glcheckbox-to-squah-commits.yml @@ -0,0 +1,5 @@ +--- +title: Add GlFormCheckbox to squash commits +merge_request: 48338 +author: +type: changed diff --git a/changelogs/unreleased/232670-steal-webauthn-background-migration.yml b/changelogs/unreleased/232670-steal-webauthn-background-migration.yml new file mode 100644 index 00000000000..a9752c57947 --- /dev/null +++ b/changelogs/unreleased/232670-steal-webauthn-background-migration.yml @@ -0,0 +1,5 @@ +--- +title: Cleanup webauthn background migration +merge_request: 46179 +author: Jan Beckmann +type: added diff --git a/changelogs/unreleased/284602-remove-issue-box-static.yml b/changelogs/unreleased/284602-remove-issue-box-static.yml new file mode 100644 index 00000000000..75cf831177f --- /dev/null +++ b/changelogs/unreleased/284602-remove-issue-box-static.yml @@ -0,0 +1,5 @@ +--- +title: Remove .issue-box from static (classic) Issuable list +merge_request: 47998 +author: Takuya Noguchi +type: performance diff --git a/changelogs/unreleased/duplicate_autocomplete_suggestions.yml b/changelogs/unreleased/duplicate_autocomplete_suggestions.yml new file mode 100644 index 00000000000..5bc818862b9 --- /dev/null +++ b/changelogs/unreleased/duplicate_autocomplete_suggestions.yml @@ -0,0 +1,5 @@ +--- +title: Fix missing item with same name in autocomplete suggestions +merge_request: 48410 +author: Paul Ungureanu @ungps +type: fixed diff --git a/config/feature_flags/development/usage_data_design_action.yml b/config/feature_flags/development/usage_data_design_action.yml deleted file mode 100644 index e013237ecca..00000000000 --- a/config/feature_flags/development/usage_data_design_action.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: usage_data_design_action -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46626 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/287630 -milestone: '13.7' -type: development -group: group::knowledge -default_enabled: true diff --git a/data/whats_new/202011230001_13_06.yml b/data/whats_new/202011230001_13_06.yml new file mode 100644 index 00000000000..faa4cf62ea9 --- /dev/null +++ b/data/whats_new/202011230001_13_06.yml @@ -0,0 +1,51 @@ +--- +- title: Auto Deploy to EC2 + body: Auto DevOps has been expanded to support deployments to Amazon Web Services. You can now deploy to AWS Cloud Compute (EC2) and take advantage of Auto DevOps, even without Kubernetes. To enable this workflow, you must enable Auto DevOps and define the AWS-typed environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION. This allows you to provision your own infrastructure by leveraging the AWS CloudFormation API. Then, you can push your previously built artifact to an AWS S3 bucket, and deploy the content to an AWS EC2 instance. Your EC2 deployment then automatically builds you a complete, automatic delivery pipeline without extra manual steps. + stage: Release + self-managed: true + gitlab-com: true + packages: [core, starter, premium, ultimate] + url: https://docs.gitlab.com/ee/ci/cloud_deployment/#custom-build-job-for-auto-devops + image_url: https://img.youtube.com/vi/4B-qSwKnacA/hqdefault.jpg + published_at: 2020-11-22 + release: 13.6 +- title: Display Code Quality severity ratings + body: The Code Quality feature in GitLab is great at showing what quality violations exist in a project or are changing in the Merge Request. However, understanding which of those violations is the most important is not clear in the GitLab interface today. With the Full Code Quality Report and Merge Request Widget, now you can see the severity rating. This makes it easy for you to understand which code quality violations are most important to resolve before merging and reduces the technical debt in your project. + stage: Verify + self-managed: true + gitlab-com: true + packages: [core, starter, premium, ultimate] + url: https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html#code-quality-widget + image_url: https://about.gitlab.com/images/13_6/code_quality_severity.png + published_at: 2020-11-22 + release: 13.6 +- title: Display code coverage data for selected projects + body: In 13.4, we released the first iteration of Code Coverage data for Groups that enables you to compare the coverage of multiple projects and download the data in a single file from a single screen. However, to analyze the data, you had to open the file to check it manually, and probably imported it into a spreadsheet for further analysis. In GitLab 13.6, you can now select specific projects in a group to see their latest coverage values directly in GitLab itself without needing to download a file or waste development time accessing code coverage data. + stage: Verify + self-managed: true + gitlab-com: true + packages: [premium, ultimate] + url: https://docs.gitlab.com/ee/user/group/repositories_analytics/index.html#latest-project-test-coverage-list + image_url: https://about.gitlab.com/images/13_6/display_selected_coverage_projects_example.png + published_at: 2020-11-22 + release: 13.6 +- title: Group-level management of project integrations + body: In GitLab 13.3, we added the ability to enable an integration across an entire instance. With GitLab 13.6, that feature is being expanded to allow integrations to be managed at the group level as well! Group owners can now add an integration to a group, and that integration will be inherited by all projects under that group. This has the potential for saving massive amounts of time, as many organizations have specific integrations that they want rolled out to every project they create. A great example of this is using our Jira integration. If you're using Jira, it's almost always across the whole company. Some of these companies have _thousands of projects_ and therefore had to configure each and every one of those integrations individually.With group-level management of project integrations, you can add the integration at each parent group, reducing the amount of configuration required by orders of magnitude! + stage: Create + self-managed: true + gitlab-com: true + packages: [core, starter, premium, ultimate] + url: https://docs.gitlab.com/ee/user/admin_area/settings/project_integration_management.html + image_url: https://about.gitlab.com/images/13_6/project-integration-inheriting-settings.png + published_at: 2020-11-22 + release: 13.6 +- title: Milestone Burnup Charts and historically accurate reporting + body: A milestone or iteration burndown chart helps track completion progress of total scope, but it doesn't provide insight into how the scope changed during the course of the timebox. Neither has it previously retained historical accuracy regarding how much of the initial committed scope of the milestone or iteration was actually completed. To solve these problems and help teams have better insights into scope creep, milestones and iterations now show a burnup chart that tracks the daily total count and weight of issues added to and completed within a given timebox. This change only applies to milestones and iterations created in GitLab 13.6 or later. Milestones created prior to 13.6 will still have access to legacy burndown charts. + stage: Plan + self-managed: true + gitlab-com: true + packages: [starter, premium, ultimate] + url: https://docs.gitlab.com/ee/user/project/milestones/burndown_and_burnup_charts.html#burnup-charts + image_url: https://about.gitlab.com/images/13_6/burnup-chart.png + published_at: 2020-11-22 + release: 13.6 diff --git a/db/migrate/20201117075742_change_webauthn_xid_length.rb b/db/migrate/20201117075742_change_webauthn_xid_length.rb new file mode 100644 index 00000000000..2d836662e01 --- /dev/null +++ b/db/migrate/20201117075742_change_webauthn_xid_length.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ChangeWebauthnXidLength < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_text_limit :webauthn_registrations, :credential_xid, 340, constraint_name: check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length_v2') + remove_text_limit :webauthn_registrations, :credential_xid, constraint_name: check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length') + end + + def down + # no-op: Danger of failling if there are records with length(credential_xid) > 255 + end +end diff --git a/db/post_migrate/20201026185514_ensure_u2f_registrations_migrated.rb b/db/post_migrate/20201026185514_ensure_u2f_registrations_migrated.rb new file mode 100644 index 00000000000..121b9fee623 --- /dev/null +++ b/db/post_migrate/20201026185514_ensure_u2f_registrations_migrated.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class EnsureU2fRegistrationsMigrated < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + BACKGROUND_MIGRATION_CLASS = 'MigrateU2fWebauthn' + BATCH_SIZE = 100 + DOWNTIME = false + + disable_ddl_transaction! + + class U2fRegistration < ActiveRecord::Base + include EachBatch + + self.table_name = 'u2f_registrations' + end + + def up + Gitlab::BackgroundMigration.steal(BACKGROUND_MIGRATION_CLASS) + + # Do a manual update in case we lost BG jobs. The expected record count should be 0 or very low. + U2fRegistration + .joins("LEFT JOIN webauthn_registrations ON webauthn_registrations.u2f_registration_id = u2f_registrations.id") + .where("webauthn_registrations.u2f_registration_id IS NULL") + .each_batch(of: BATCH_SIZE) do |batch, index| + batch.each do |record| + Gitlab::BackgroundMigration::MigrateU2fWebauthn.new.perform(record.id, record.id) + rescue => e + Gitlab::ErrorTracking.track_exception(e, u2f_registration_id: record.id) + end + end + end + + def down + # no-op (we can't "unsteal" migrations) + end +end diff --git a/db/schema_migrations/20201026185514 b/db/schema_migrations/20201026185514 new file mode 100644 index 00000000000..f6bdd06e501 --- /dev/null +++ b/db/schema_migrations/20201026185514 @@ -0,0 +1 @@ +a9ae0161c40b9c72371d6eb992bd0da8c3698e7784357faac0821e3f513e48d2
\ No newline at end of file diff --git a/db/schema_migrations/20201117075742 b/db/schema_migrations/20201117075742 new file mode 100644 index 00000000000..b3efeee7e0b --- /dev/null +++ b/db/schema_migrations/20201117075742 @@ -0,0 +1 @@ +a39bad8b213833c84370cf64188a3ce444fd8aeeff239c29f5f2f633d94ac6bb
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 97e669029b7..5cb98ddbd19 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17571,8 +17571,8 @@ CREATE TABLE webauthn_registrations ( name text NOT NULL, public_key text NOT NULL, u2f_registration_id integer, - CONSTRAINT check_242f0cc65c CHECK ((char_length(credential_xid) <= 255)), - CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255)) + CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255)), + CONSTRAINT check_e54008d9ce CHECK ((char_length(credential_xid) <= 340)) ); CREATE SEQUENCE webauthn_registrations_id_seq diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md index e7d67593a09..618f9010b4d 100644 --- a/doc/development/testing_guide/ci.md +++ b/doc/development/testing_guide/ci.md @@ -12,8 +12,8 @@ Our current CI parallelization setup is as follows: 1. The `retrieve-tests-metadata` job in the `prepare` stage ensures we have a `knapsack/report-master.json` file: - - The `knapsack/report-master.json` file is fetched from the latest `master` pipeline which runs `update-tests-metadata` - (for now it's the 2-hourly scheduled master pipeline), if it's not here we initialize the file with `{}`. + - The `knapsack/report-master.json` file is fetched from S3, if it's not here + we initialize the file with `{}`. 1. Each `[rspec|rspec-ee] [unit|integration|system|geo] n m` job are run with `knapsack rspec` and should have an evenly distributed share of tests: - It works because the jobs have access to the `knapsack/report-master.json` @@ -25,7 +25,7 @@ Our current CI parallelization setup is as follows: 1. The `update-tests-metadata` job (which only runs on scheduled pipelines for [the canonical project](https://gitlab.com/gitlab-org/gitlab) takes all the `knapsack/rspec*_pg_*.json` files and merge them all together into a single - `knapsack/report-master.json` file that is saved as artifact. + `knapsack/report-master.json` file that is then uploaded to S3. After that, the next pipeline will use the up-to-date `knapsack/report-master.json` file. diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 1ad6298caeb..49b444bd871 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -38,13 +38,15 @@ GitLab.com uses the SAML NameID to identify users. The NameID element: - Must be unique to each user. - Must be a persistent value that will never change, such as a randomly generated unique user ID. - Is case sensitive. The NameID must match exactly on subsequent login attempts, so should not rely on user input that could change between upper and lower case. -- Should not be an email address or username. We strongly recommend against these as it is hard to guarantee they will never change, for example when a person's name changes. Email addresses are also case-insensitive, which can result in users being unable to sign in. +- Should not be an email address or username. We strongly recommend against these as it's hard to + guarantee it doesn't ever change, for example, when a person's name changes. Email addresses are + also case-insensitive, which can result in users being unable to sign in. The relevant field name and recommended value for supported providers are in the [provider specific notes](#providers). appropriate corresponding field. CAUTION: **Warning:** -Once users have signed into GitLab using the SSO SAML setup, changing the `NameID` will break the configuration and potentially lock users out of the GitLab group. +Once users have signed into GitLab using the SSO SAML setup, changing the `NameID` breaks the configuration and potentially locks users out of the GitLab group. #### NameID Format @@ -56,11 +58,11 @@ GitLab provides metadata XML that can be used to configure your Identity Provide 1. Navigate to the group and click **Settings > SAML SSO**. 1. Copy the provided **GitLab metadata URL**. -1. Follow your Identity Provider's documentation and paste the metadata URL when it is requested. +1. Follow your Identity Provider's documentation and paste the metadata URL when it's requested. ## Configuring GitLab -Once you've set up your identity provider to work with GitLab, you'll need to configure GitLab to use it for authentication: +After you set up your identity provider to work with GitLab, you must configure GitLab to use it for authentication: 1. Navigate to the group's **Settings > SAML SSO**. 1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign-on URL** field. @@ -79,14 +81,14 @@ Please note that the certificate [fingerprint algorithm](#additional-providers-a - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5291) in GitLab 11.8. - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/9255) in GitLab 11.11 with ongoing enforcement in the GitLab UI. -With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users cannot be added manually, and may only access project/group resources via the UI by signing in through the SSO URL. +With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users can't be added manually, and may only access project/group resources via the UI by signing in through the SSO URL. -However, users will not be prompted to sign in through SSO on each visit. GitLab will check whether a user has authenticated through SSO, and will only prompt the user to sign in via SSO if the session has expired. +However, users are not prompted to sign in through SSO on each visit. GitLab checks whether a user has authenticated through SSO, and only prompts the user to sign in via SSO if the session has expired. You can see more information about how long a session is valid in our [user profile documentation](../../profile/#why-do-i-keep-getting-signed-out). We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152). -When SSO enforcement is enabled for a group, users cannot share a project in the group outside the top-level group, even if the project is forked. +When SSO enforcement is enabled for a group, users can't share a project in the group outside the top-level group, even if the project is forked. ## Providers @@ -192,7 +194,7 @@ If the information you need isn't listed above you may wish to check our [troubl Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup). -When a user tries to sign in with Group SSO, they will need an account that's configured with one of the following: +When a user tries to sign in with Group SSO, they need an account that's configured with one of the following: - [SCIM](scim_setup.md). - [Group-managed accounts](group_managed_accounts.md). @@ -203,18 +205,18 @@ When a user tries to sign in with Group SSO, they will need an account that's co To link SAML to your existing GitLab.com account: 1. Sign in to your GitLab.com account. -1. Locate and visit the **GitLab single sign-on URL** for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page. If the sign-in URL is configured, users can connect to the GitLab app from the Identity Provider. +1. Locate and visit the **GitLab single sign-on URL** for the group you're signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page. If the sign-in URL is configured, users can connect to the GitLab app from the Identity Provider. 1. Click **Authorize**. 1. Enter your credentials on the Identity Provider if prompted. -1. You will be redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com. +1. You are then redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com. -On subsequent visits, you should be able to go [sign in to GitLab.com with SAML](#signing-in-to-gitlabcom-with-saml) or by visiting links directly. If the **enforce SSO** option is turned on, you will be redirected to sign in through the identity provider. +On subsequent visits, you should be able to go [sign in to GitLab.com with SAML](#signing-in-to-gitlabcom-with-saml) or by visiting links directly. If the **enforce SSO** option is turned on, you are then redirected to sign in through the identity provider. ### Signing in to GitLab.com with SAML 1. Sign in to your identity provider. 1. From the list of apps, click on the "GitLab.com" app (The name is set by the administrator of the identity provider). -1. You will be signed in to GitLab.com and redirected to the group. +1. You are then signed in to GitLab.com and redirected to the group. ### Role @@ -238,7 +240,7 @@ Users can unlink SAML for a group from their profile page. This can be helpful i - You no longer want a group to be able to sign you in to GitLab.com. - Your SAML NameID has changed and so GitLab can no longer find your user. -For example, to unlink the `MyOrg` account, the following **Disconnect** button will be available under **Profile > Accounts**: +For example, to unlink the `MyOrg` account, the following **Disconnect** button is available under **Profile > Accounts**: ![Unlink Group SAML](img/unlink_group_saml.png) @@ -286,7 +288,7 @@ access. | Service Provider | SAML considers GitLab to be a service provider. | | Assertion | A piece of information about a user's identity, such as their name or role. Also know as claims or attributes. | | SSO | Single Sign On. | -| Assertion consumer service URL | The callback on GitLab where users will be redirected after successfully authenticating with the identity provider. | +| Assertion consumer service URL | The callback on GitLab where users are redirected after successfully authenticating with the identity provider. | | Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". | | Certificate fingerprint | Used to confirm that communications over SAML are secure by checking that the server is signing communications with the correct certificate. Also known as a certificate thumbprint. | diff --git a/lib/gitlab/database/batch_count.rb b/lib/gitlab/database/batch_count.rb index 6f79e965cd5..5a506da0d05 100644 --- a/lib/gitlab/database/batch_count.rb +++ b/lib/gitlab/database/batch_count.rb @@ -49,6 +49,8 @@ module Gitlab MAX_ALLOWED_LOOPS = 10_000 SLEEP_TIME_IN_SECONDS = 0.01 # 10 msec sleep ALLOWED_MODES = [:itself, :distinct].freeze + FALLBACK_FINISH = 0 + OFFSET_BY_ONE = 1 # Each query should take < 500ms https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22705 DEFAULT_DISTINCT_BATCH_SIZE = 10_000 @@ -65,7 +67,7 @@ module Gitlab (@operation == :count && batch_size <= MIN_REQUIRED_BATCH_SIZE) || (@operation == :sum && batch_size < DEFAULT_SUM_BATCH_SIZE) || (finish - start) / batch_size >= MAX_ALLOWED_LOOPS || - start > finish + start >= finish end def count(batch_size: nil, mode: :itself, start: nil, finish: nil) @@ -85,11 +87,13 @@ module Gitlab results = nil batch_start = start - while batch_start <= finish - batch_relation = build_relation_batch(batch_start, batch_start + batch_size, mode) + while batch_start < finish + batch_end = [batch_start + batch_size, finish].min + batch_relation = build_relation_batch(batch_start, batch_end, mode) + begin results = merge_results(results, batch_relation.send(@operation, *@operation_args)) # rubocop:disable GitlabSecurity/PublicSend - batch_start += batch_size + batch_start = batch_end rescue ActiveRecord::QueryCanceled => error # retry with a safe batch size & warmer cache if batch_size >= 2 * MIN_REQUIRED_BATCH_SIZE @@ -99,6 +103,7 @@ module Gitlab return FALLBACK end end + sleep(SLEEP_TIME_IN_SECONDS) end @@ -138,7 +143,7 @@ module Gitlab end def actual_finish(finish) - finish || @relation.unscope(:group, :having).maximum(@column) || 0 + (finish || @relation.unscope(:group, :having).maximum(@column) || FALLBACK_FINISH) + OFFSET_BY_ONE end def check_mode!(mode) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8fc93e4810d..7ec22748d11 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17794,7 +17794,7 @@ msgstr "" msgid "MissingSSHKeyWarningLink|Don't show again" msgstr "" -msgid "MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile" +msgid "MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile" msgstr "" msgid "ModalButton|Add projects" @@ -21111,9 +21111,6 @@ msgstr "" msgid "Project cannot be shared with the group it is in or one of its ancestors." msgstr "" -msgid "Project clone URL" -msgstr "" - msgid "Project configuration, excluding integrations" msgstr "" @@ -23088,6 +23085,9 @@ msgstr "" msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete." msgstr "" +msgid "Repository clone URL" +msgstr "" + msgid "Repository files count over the limit" msgstr "" @@ -24252,6 +24252,9 @@ msgstr "" msgid "SecurityReports|Scan details" msgstr "" +msgid "SecurityReports|Scanner" +msgstr "" + msgid "SecurityReports|Security Dashboard" msgstr "" @@ -31430,10 +31433,7 @@ msgstr "" msgid "You won't be able to create new projects because you have reached your project limit." msgstr "" -msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" -msgstr "" - -msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" +msgid "You won't be able to pull or push repositories via %{protocol} until you %{set_password_link} on your account" msgstr "" msgid "You'll be charged for %{true_up_link_start}users over license%{link_end} on a quarterly or annual basis, depending on the terms of your agreement." diff --git a/qa/qa/page/component/legacy_clone_panel.rb b/qa/qa/page/component/legacy_clone_panel.rb index ebab9fd708c..f15d159a712 100644 --- a/qa/qa/page/component/legacy_clone_panel.rb +++ b/qa/qa/page/component/legacy_clone_panel.rb @@ -12,7 +12,7 @@ module QA base.view 'app/views/shared/_clone_panel.html.haml' do element :clone_dropdown element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern - element :project_repository_location, 'text_field_tag :project_clone' # rubocop:disable QA/ElementWithPattern + element :clone_url, 'text_field_tag :clone_url' # rubocop:disable QA/ElementWithPattern end end @@ -28,7 +28,7 @@ module QA end def repository_location - Git::Location.new(find('#project_clone').value) + Git::Location.new(find('#clone_url').value) end private diff --git a/scripts/api/cancel_pipeline b/scripts/api/cancel_pipeline deleted file mode 100755 index 0965877a69a..00000000000 --- a/scripts/api/cancel_pipeline +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'rubygems' -require 'gitlab' -require 'optparse' -require_relative 'get_job_id' - -class CancelPipeline - DEFAULT_OPTIONS = { - project: ENV['CI_PROJECT_ID'], - pipeline_id: ENV['CI_PIPELINE_ID'], - api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] - }.freeze - - def initialize(options) - @project = options.delete(:project) - @pipeline_id = options.delete(:pipeline_id) - - Gitlab.configure do |config| - config.endpoint = 'https://gitlab.com/api/v4' - config.private_token = options.delete(:api_token) - end - end - - def execute - Gitlab.cancel_pipeline(project, pipeline_id) - end - - private - - attr_reader :project, :pipeline_id -end - -if $0 == __FILE__ - options = CancelPipeline::DEFAULT_OPTIONS.dup - - OptionParser.new do |opts| - opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value| - options[:project] = value - end - - opts.on("-i", "--pipeline-id PIPELINE_ID", String, "A pipeline ID (defaults to $CI_PIPELINE_ID)") do |value| - options[:pipeline_id] = value - end - - opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value| - options[:api_token] = value - end - - opts.on("-h", "--help", "Prints this help") do - puts opts - exit - end - end.parse! - - CancelPipeline.new(options).execute -end diff --git a/scripts/api/download_job_artifact b/scripts/api/download_job_artifact deleted file mode 100755 index 9ac24ff624d..00000000000 --- a/scripts/api/download_job_artifact +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'rubygems' -require 'optparse' -require 'fileutils' -require 'uri' -require 'cgi' -require 'net/http' - -class ArtifactFinder - DEFAULT_OPTIONS = { - project: ENV['CI_PROJECT_ID'], - api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] - }.freeze - - def initialize(options) - @project = options.delete(:project) - @job_id = options.delete(:job_id) - @api_token = options.delete(:api_token) - @artifact_path = options.delete(:artifact_path) - end - - def execute - url = "https://gitlab.com/api/v4/projects/#{CGI.escape(project)}/jobs/#{job_id}/artifacts" - - if artifact_path - FileUtils.mkdir_p(File.dirname(artifact_path)) - url += "/#{artifact_path}" - end - - fetch(url) - end - - private - - attr_reader :project, :job_id, :api_token, :artifact_path - - def fetch(uri_str, limit = 10) - raise 'Too many HTTP redirects' if limit == 0 - - uri = URI(uri_str) - request = Net::HTTP::Get.new(uri) - request['Private-Token'] = api_token - - Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| - http.request(request) do |response| - case response - when Net::HTTPSuccess then - File.open(artifact_path || 'artifacts.zip', 'w') do |file| - response.read_body(&file.method(:write)) - end - when Net::HTTPRedirection then - location = response['location'] - warn "Redirected (#{limit - 1} redirections remaining)." - fetch(location, limit - 1) - else - raise "Unexpected response: #{response.value}" - end - end - end - end -end - -if $0 == __FILE__ - options = ArtifactFinder::DEFAULT_OPTIONS.dup - - OptionParser.new do |opts| - opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value| - options[:project] = value - end - - opts.on("-j", "--job-id JOB_ID", String, "A job ID") do |value| - options[:job_id] = value - end - - opts.on("-a", "--artifact-path ARTIFACT_PATH", String, "A valid artifact path") do |value| - options[:artifact_path] = value - end - - opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value| - options[:api_token] = value - end - - opts.on("-h", "--help", "Prints this help") do - puts opts - exit - end - end.parse! - - ArtifactFinder.new(options).execute -end diff --git a/scripts/api/get_job_id b/scripts/api/get_job_id deleted file mode 100755 index 2324f6ca9d3..00000000000 --- a/scripts/api/get_job_id +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'rubygems' -require 'gitlab' -require 'optparse' - -class JobFinder - DEFAULT_OPTIONS = { - project: ENV['CI_PROJECT_ID'], - pipeline_id: ENV['CI_PIPELINE_ID'], - pipeline_query: {}, - job_query: {}, - api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] - }.freeze - - def initialize(options) - @project = options.delete(:project) - @pipeline_query = options.delete(:pipeline_query) - @job_query = options.delete(:job_query) - @pipeline_id = options.delete(:pipeline_id) - @job_name = options.delete(:job_name) - - Gitlab.configure do |config| - config.endpoint = 'https://gitlab.com/api/v4' - config.private_token = options.delete(:api_token) - end - end - - def execute - find_job_with_filtered_pipelines || find_job_in_pipeline - end - - private - - attr_reader :project, :pipeline_query, :job_query, :pipeline_id, :job_name - - def find_job_with_filtered_pipelines - return if pipeline_query.empty? - - Gitlab.pipelines(project, pipeline_query_params).auto_paginate do |pipeline| - Gitlab.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job| - return job if job.name == job_name # rubocop:disable Cop/AvoidReturnFromBlocks - end - end - - raise 'Job not found!' - end - - def find_job_in_pipeline - return unless pipeline_id - - Gitlab.pipeline_jobs(project, pipeline_id, job_query_params).auto_paginate do |job| - return job if job.name == job_name # rubocop:disable Cop/AvoidReturnFromBlocks - end - - raise 'Job not found!' - end - - def pipeline_query_params - @pipeline_query_params ||= { per_page: 100, **pipeline_query } - end - - def job_query_params - @job_query_params ||= { per_page: 100, **job_query } - end -end - -if $0 == __FILE__ - options = JobFinder::DEFAULT_OPTIONS.dup - - OptionParser.new do |opts| - opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value| - options[:project] = value - end - - opts.on("-i", "--pipeline-id pipeline_id", String, "A pipeline ID (defaults to $CI_PIPELINE_ID)") do |value| - options[:pipeline_id] = value - end - - opts.on("-q", "--pipeline-query pipeline_query", String, "Query to pass to the Pipeline API request") do |value| - options[:pipeline_query].merge!(Hash[*value.split('=')]) - end - - opts.on("-Q", "--job-query job_query", String, "Query to pass to the Job API request") do |value| - options[:job_query].merge!(Hash[*value.split('=')]) - end - - opts.on("-j", "--job-name job_name", String, "A job name that needs to exist in the found pipeline") do |value| - options[:job_name] = value - end - - opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value| - options[:api_token] = value - end - - opts.on("-h", "--help", "Prints this help") do - puts opts - exit - end - end.parse! - - job = JobFinder.new(options).execute - - return if job.nil? - - puts job.id -end diff --git a/scripts/api/play_job b/scripts/api/play_job deleted file mode 100755 index 199f7e65633..00000000000 --- a/scripts/api/play_job +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'rubygems' -require 'gitlab' -require 'optparse' -require_relative 'get_job_id' - -class PlayJob - DEFAULT_OPTIONS = { - project: ENV['CI_PROJECT_ID'], - pipeline_id: ENV['CI_PIPELINE_ID'], - api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] - }.freeze - - def initialize(options) - @project = options.delete(:project) - @options = options - - Gitlab.configure do |config| - config.endpoint = 'https://gitlab.com/api/v4' - config.private_token = options.fetch(:api_token) - end - end - - def execute - job = JobFinder.new(project, options.slice(:api_token, :pipeline_id, :job_name).merge(scope: 'manual')).execute - - Gitlab.job_play(project, job.id) - end - - private - - attr_reader :project, :options -end - -if $0 == __FILE__ - options = PlayJob::DEFAULT_OPTIONS.dup - - OptionParser.new do |opts| - opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value| - options[:project] = value - end - - opts.on("-j", "--job-name JOB_NAME", String, "A job name that needs to exist in the found pipeline") do |value| - options[:job_name] = value - end - - opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value| - options[:api_token] = value - end - - opts.on("-h", "--help", "Prints this help") do - puts opts - exit - end - end.parse! - - PlayJob.new(options).execute -end diff --git a/scripts/get-job-id b/scripts/get-job-id new file mode 100755 index 00000000000..a5d34dc545b --- /dev/null +++ b/scripts/get-job-id @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'gitlab' +require 'optparse' + +# +# Configure credentials to be used with gitlab gem +# +Gitlab.configure do |config| + config.endpoint = 'https://gitlab.com/api/v4' + config.private_token = ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] +end + +options = {} +OptionParser.new do |opts| + opts.on("-s", "--scope=SCOPE", "Find job with matching scope") do |scope| + options[:scope] = scope + end +end.parse! + +class PipelineJobFinder + def initialize(project_id, pipeline_id, job_name, options) + @project_id = project_id + @pipeline_id = pipeline_id + @job_name = job_name + @options = options + end + + def execute + Gitlab.pipeline_jobs(@project_id, @pipeline_id, @options).auto_paginate do |job| + break job if job.name == @job_name + end + end +end + +project_id, pipeline_id, job_name = ARGV + +job = PipelineJobFinder.new(project_id, pipeline_id, job_name, options).execute + +return if job.nil? + +puts job.id diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh index 0f14b702de2..5f003d032b7 100644 --- a/scripts/rspec_helpers.sh +++ b/scripts/rspec_helpers.sh @@ -1,39 +1,44 @@ #!/usr/bin/env bash function retrieve_tests_metadata() { - mkdir -p crystalball/ knapsack/ rspec_flaky/ rspec_profiling/ - - local project_path="gitlab-org/gitlab" - local test_metadata_job_id - - # Ruby - test_metadata_job_id=$(scripts/api/get_job_id --project "${project_path}" -q "status=success" -q "ref=master" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata") + mkdir -p knapsack/ rspec_flaky/ rspec_profiling/ if [[ ! -f "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ]]; then - scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" + wget -O "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" fi if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then - scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}" + wget -O "${FLAKY_RSPEC_SUITE_REPORT_PATH}" "http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}" fi - - # FIXME: We will need to find a pipeline where the $RSPEC_PACKED_TESTS_MAPPING_PATH.gz actually exists (Crystalball only runs every two-hours, but the `update-tests-metadata` runs for all `master` pipelines...). - # if [[ ! -f "${RSPEC_PACKED_TESTS_MAPPING_PATH}" ]]; then - # (scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}" - # fi - # - # scripts/unpack-test-mapping "${RSPEC_PACKED_TESTS_MAPPING_PATH}" "${RSPEC_TESTS_MAPPING_PATH}" } function update_tests_metadata() { echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" knapsack/rspec*.json + if [[ -n "${TESTS_METADATA_S3_BUCKET}" ]]; then + if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then + scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" + else + echo "Not uplaoding report to S3 as the pipeline is not a scheduled one." + fi + fi + rm -f knapsack/rspec*.json - export FLAKY_RSPEC_GENERATE_REPORT="true" scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH}" rspec_flaky/all_*.json + + export FLAKY_RSPEC_GENERATE_REPORT="true" scripts/flaky_examples/prune-old-flaky-examples "${FLAKY_RSPEC_SUITE_REPORT_PATH}" + + if [[ -n ${TESTS_METADATA_S3_BUCKET} ]]; then + if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then + scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${FLAKY_RSPEC_SUITE_REPORT_PATH}" + else + echo "Not uploading report to S3 as the pipeline is not a scheduled one." + fi + fi + rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then @@ -43,6 +48,16 @@ function update_tests_metadata() { fi } +function retrieve_tests_mapping() { + mkdir -p crystalball/ + + if [[ ! -f "${RSPEC_PACKED_TESTS_MAPPING_PATH}" ]]; then + (wget -O "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" "http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}" + fi + + scripts/unpack-test-mapping "${RSPEC_PACKED_TESTS_MAPPING_PATH}" "${RSPEC_TESTS_MAPPING_PATH}" +} + function update_tests_mapping() { if ! crystalball_rspec_data_exists; then echo "No crystalball rspec data found." @@ -50,9 +65,20 @@ function update_tests_mapping() { fi scripts/generate-test-mapping "${RSPEC_TESTS_MAPPING_PATH}" crystalball/rspec*.yml + scripts/pack-test-mapping "${RSPEC_TESTS_MAPPING_PATH}" "${RSPEC_PACKED_TESTS_MAPPING_PATH}" + gzip "${RSPEC_PACKED_TESTS_MAPPING_PATH}" - rm -f crystalball/rspec*.yml "${RSPEC_PACKED_TESTS_MAPPING_PATH}" + + if [[ -n "${TESTS_METADATA_S3_BUCKET}" ]]; then + if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then + scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" + else + echo "Not uploading report to S3 as the pipeline is not a scheduled one." + fi + fi + + rm -f crystalball/rspec*.yml } function crystalball_rspec_data_exists() { diff --git a/scripts/utils.sh b/scripts/utils.sh index 4d6088e94a8..3829bcdf24e 100644 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -87,14 +87,65 @@ function echosuccess() { fi } +function get_job_id() { + local job_name="${1}" + local query_string="${2:+&${2}}" + local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}" + if [ -z "${api_token}" ]; then + echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN." + return + fi + + local max_page=3 + local page=1 + + while true; do + local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}" + echoinfo "GET ${url}" + + local job_id + job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last") + [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break + + let "page++" + done + + if [[ "${job_id}" == "null" ]]; then # jq prints "null" for non-existent attribute + echoerr "The '${job_name}' job ID couldn't be retrieved!" + else + echoinfo "The '${job_name}' job ID is ${job_id}" + echo "${job_id}" + fi +} + +function play_job() { + local job_name="${1}" + local job_id + job_id=$(get_job_id "${job_name}" "scope=manual"); + if [ -z "${job_id}" ]; then return; fi + + local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}" + if [ -z "${api_token}" ]; then + echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN." + return + fi + + local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play" + echoinfo "POST ${url}" + + local job_url + job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".web_url") + echoinfo "Manual job '${job_name}' started at: ${job_url}" +} + function fail_pipeline_early() { local dont_interrupt_me_job_id - dont_interrupt_me_job_id=$(scripts/api/get_job_id --job-query "scope=success" --job-name "dont-interrupt-me") + dont_interrupt_me_job_id=$(get_job_id 'dont-interrupt-me' 'scope=success') if [[ -n "${dont_interrupt_me_job_id}" ]]; then echoinfo "This pipeline cannot be interrupted due to \`dont-interrupt-me\` job ${dont_interrupt_me_job_id}" else echoinfo "Failing pipeline early for fast feedback due to test failures in rspec fail-fast." - scripts/api/cancel_pipeline + curl --request POST --header "PRIVATE-TOKEN: ${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}" "https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/cancel" fi } diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb index 637ac72eca2..ff969a7786d 100644 --- a/spec/features/profiles/active_sessions_spec.rb +++ b/spec/features/profiles/active_sessions_spec.rb @@ -56,8 +56,8 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do visit profile_active_sessions_path expect(page).to( - have_selector('ul.list-group li.list-group-item', { text: 'Signed in on', - count: 2 })) + have_selector('ul.list-group li.list-group-item', text: 'Signed in on', + count: 2)) expect(page).to have_content( '127.0.0.1 ' \ diff --git a/spec/features/projects/show/no_password_spec.rb b/spec/features/projects/show/no_password_spec.rb index 79cd65e5406..d18ff75b324 100644 --- a/spec/features/projects/show/no_password_spec.rb +++ b/spec/features/projects/show/no_password_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'No Password Alert' do let(:user) { create(:user) } it 'shows no alert' do - expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account" + expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account" end end @@ -23,7 +23,7 @@ RSpec.describe 'No Password Alert' do let(:user) { create(:user, password_automatically_set: true) } it 'shows a password alert' do - expect(page).to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account" + expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account" end end end @@ -41,7 +41,7 @@ RSpec.describe 'No Password Alert' do gitlab_sign_in_via('saml', user, 'my-uid') visit project_path(project) - expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account" + expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account" end end @@ -51,7 +51,7 @@ RSpec.describe 'No Password Alert' do gitlab_sign_in_via('saml', user, 'my-uid') visit project_path(project) - expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account" + expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account" end end end @@ -65,7 +65,7 @@ RSpec.describe 'No Password Alert' do end it 'shows no alert' do - expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you" + expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you" end end end diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb deleted file mode 100644 index 83679c6bd1d..00000000000 --- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Projects > Wiki > User views Git access wiki page' do - let(:user) { create(:user) } - let(:project) { create(:project, :wiki_repo, :public) } - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, title: 'home', content: '[some link](other-page)') } - - before do - sign_in(user) - end - - it 'Visit Wiki Page Current Commit' do - visit project_wiki_path(project, wiki_page) - - click_link 'Clone repository' - expect(page).to have_text("Clone repository #{project.wiki.full_path}") - expect(page).to have_text(project.wiki.http_url_to_repo) - end -end diff --git a/spec/features/projects/wikis_spec.rb b/spec/features/projects/wikis_spec.rb index 1c66ad81145..621f8c71b20 100644 --- a/spec/features/projects/wikis_spec.rb +++ b/spec/features/projects/wikis_spec.rb @@ -17,4 +17,5 @@ RSpec.describe 'Project wikis' do it_behaves_like 'User views a wiki page' it_behaves_like 'User views wiki pages' it_behaves_like 'User views wiki sidebar' + it_behaves_like 'User views Git access wiki page' end diff --git a/spec/finders/ci/pipeline_schedules_finder_spec.rb b/spec/finders/ci/pipeline_schedules_finder_spec.rb index 57842bbecd7..535c684289e 100644 --- a/spec/finders/ci/pipeline_schedules_finder_spec.rb +++ b/spec/finders/ci/pipeline_schedules_finder_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Ci::PipelineSchedulesFinder do let!(:active_schedule) { create(:ci_pipeline_schedule, project: project) } let!(:inactive_schedule) { create(:ci_pipeline_schedule, :inactive, project: project) } - subject { described_class.new(project).execute(params) } + subject { described_class.new(project).execute(**params) } describe "#execute" do context 'when the scope is nil' do diff --git a/spec/finders/feature_flags_finder_spec.rb b/spec/finders/feature_flags_finder_spec.rb index 870447a1286..cab1094672b 100644 --- a/spec/finders/feature_flags_finder_spec.rb +++ b/spec/finders/feature_flags_finder_spec.rb @@ -18,7 +18,7 @@ RSpec.describe FeatureFlagsFinder do end describe '#execute' do - subject { finder.execute(args) } + subject { finder.execute(**args) } let!(:feature_flag_1) { create(:operations_feature_flag, name: 'flag-a', project: project) } let!(:feature_flag_2) { create(:operations_feature_flag, name: 'flag-b', project: project) } diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js index 5326d63cb8a..f9490ac77ff 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js @@ -1,4 +1,5 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlFormCheckbox } from '@gitlab/ui'; import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n'; @@ -20,17 +21,15 @@ describe('Squash before merge component', () => { wrapper.destroy(); }); - const findLabel = () => wrapper.find('[data-testid="squashLabel"]'); + const findCheckbox = () => wrapper.find(GlFormCheckbox); describe('checkbox', () => { - const findCheckbox = () => wrapper.find('.js-squash-checkbox'); - it('is unchecked if passed value prop is false', () => { createComponent({ value: false, }); - expect(findCheckbox().element.checked).toBeFalsy(); + expect(findCheckbox().vm.$attrs.checked).toBe(false); }); it('is checked if passed value prop is true', () => { @@ -38,22 +37,7 @@ describe('Squash before merge component', () => { value: true, }); - expect(findCheckbox().element.checked).toBeTruthy(); - }); - - it('changes value on click', done => { - createComponent({ - value: false, - }); - - findCheckbox().element.checked = true; - - findCheckbox().trigger('change'); - - wrapper.vm.$nextTick(() => { - expect(findCheckbox().element.checked).toBeTruthy(); - done(); - }); + expect(findCheckbox().vm.$attrs.checked).toBe(true); }); it('is disabled if isDisabled prop is true', () => { @@ -62,31 +46,12 @@ describe('Squash before merge component', () => { isDisabled: true, }); - expect(findCheckbox().attributes('disabled')).toBeTruthy(); - }); - }); - - describe('label', () => { - describe.each` - isDisabled | expectation - ${true} | ${'grays out text if it is true'} - ${false} | ${'does not gray out text if it is false'} - `('isDisabled prop', ({ isDisabled, expectation }) => { - beforeEach(() => { - createComponent({ - value: false, - isDisabled, - }); - }); - - it(expectation, () => { - expect(findLabel().classes('gl-text-gray-400')).toBe(isDisabled); - }); + expect(findCheckbox().vm.$attrs.disabled).toBe(true); }); }); describe('tooltip', () => { - const tooltipTitle = () => findLabel().attributes('title'); + const tooltipTitle = () => findCheckbox().attributes('title'); it('does not render when isDisabled is false', () => { createComponent({ @@ -114,7 +79,7 @@ describe('Squash before merge component', () => { const aboutLink = wrapper.find('a'); - expect(aboutLink.exists()).toBeFalsy(); + expect(aboutLink.exists()).toBe(false); }); it('is rendered if help path is passed', () => { @@ -125,7 +90,7 @@ describe('Squash before merge component', () => { const aboutLink = wrapper.find('a'); - expect(aboutLink.exists()).toBeTruthy(); + expect(aboutLink.exists()).toBe(true); }); it('should have a correct help path if passed', () => { diff --git a/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb index f74f9186743..acd7070d0d3 100644 --- a/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb +++ b/spec/graphql/mutations/alert_management/http_integration/destroy_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy do specify { expect(described_class).to require_graphql_authorizations(:admin_operations) } describe '#resolve' do - subject(:resolve) { mutation_for(project, current_user).resolve(args) } + subject(:resolve) { mutation_for(project, current_user).resolve(**args) } context 'user has access to project' do before do diff --git a/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb index d3ffb2abb47..96974c2aa6f 100644 --- a/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb +++ b/spec/graphql/mutations/alert_management/http_integration/reset_token_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken do specify { expect(described_class).to require_graphql_authorizations(:admin_operations) } describe '#resolve' do - subject(:resolve) { mutation_for(project, current_user).resolve(args) } + subject(:resolve) { mutation_for(project, current_user).resolve(**args) } context 'user has sufficient access to project' do before do diff --git a/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb index 45d92695e06..ddf23909035 100644 --- a/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb +++ b/spec/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken do specify { expect(described_class).to require_graphql_authorizations(:admin_project) } describe '#resolve' do - subject(:resolve) { mutation_for(project, current_user).resolve(args) } + subject(:resolve) { mutation_for(project, current_user).resolve(**args) } context 'user has sufficient access to project' do before do diff --git a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb index 08761ce64c2..8465393f299 100644 --- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb +++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) } describe '#resolve' do - subject(:resolve) { mutation_for(project, current_user).resolve(args) } + subject(:resolve) { mutation_for(project, current_user).resolve(**args) } context 'user has access to project' do before do diff --git a/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb b/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb index 71c43ed826c..ded5776b3cb 100644 --- a/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb +++ b/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb @@ -34,18 +34,18 @@ RSpec.describe Mutations::Boards::Issues::IssueMoveList do end subject do - mutation.resolve(params.merge(move_params)) + mutation.resolve(**params.merge(move_params)) end describe '#ready?' do it 'raises an error if required arguments are missing' do - expect { mutation.ready?(params) } + expect { mutation.ready?(**params) } .to raise_error(Gitlab::Graphql::Errors::ArgumentError, "At least one of the arguments " \ "fromListId, toListId, afterId or beforeId is required") end it 'raises an error if only one of fromListId and toListId is present' do - expect { mutation.ready?(params.merge(from_list_id: list1.id)) } + expect { mutation.ready?(**params.merge(from_list_id: list1.id)) } .to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Both fromListId and toListId must be present' ) diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb index 9c6016e0af4..e22fb951172 100644 --- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb +++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } describe '#resolve' do - subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(params) } + subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(**params) } RSpec.shared_examples 'returning a success' do it 'returns the container expiration policy with no errors' do diff --git a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb index 2e5d41a8f1e..162b1249ab5 100644 --- a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb +++ b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Mutations::Discussions::ToggleResolve do describe '#resolve' do subject do - mutation.resolve({ id: id_arg, resolve: resolve_arg }) + mutation.resolve(id: id_arg, resolve: resolve_arg) end let(:id_arg) { discussion.to_global_id.to_s } diff --git a/spec/graphql/mutations/issues/create_spec.rb b/spec/graphql/mutations/issues/create_spec.rb index 57658f6b358..422ad40a9cb 100644 --- a/spec/graphql/mutations/issues/create_spec.rb +++ b/spec/graphql/mutations/issues/create_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Mutations::Issues::Create do project.add_guest(assignee2) end - subject { mutation.resolve(mutation_params) } + subject { mutation.resolve(**mutation_params) } context 'when the user does not have permission to create an issue' do it 'raises an error' do @@ -117,7 +117,7 @@ RSpec.describe Mutations::Issues::Create do end it 'raises exception when mutually exclusive params are given' do - expect { mutation.ready?(mutation_params) } + expect { mutation.ready?(**mutation_params) } .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/) end end @@ -128,7 +128,7 @@ RSpec.describe Mutations::Issues::Create do end it 'raises exception when mutually exclusive params are given' do - expect { mutation.ready?(mutation_params) } + expect { mutation.ready?(**mutation_params) } .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter/) end end @@ -139,7 +139,7 @@ RSpec.describe Mutations::Issues::Create do end it 'raises exception when mutually exclusive params are given' do - expect { mutation.ready?(mutation_params) }.not_to raise_error + expect { mutation.ready?(**mutation_params) }.not_to raise_error end end end diff --git a/spec/graphql/mutations/labels/create_spec.rb b/spec/graphql/mutations/labels/create_spec.rb index 8b284816d63..b2dd94f31bb 100644 --- a/spec/graphql/mutations/labels/create_spec.rb +++ b/spec/graphql/mutations/labels/create_spec.rb @@ -58,7 +58,7 @@ RSpec.describe Mutations::Labels::Create do end describe '#ready?' do - subject { mutation.ready?(attributes.merge(extra_params)) } + subject { mutation.ready?(**attributes.merge(extra_params)) } context 'when passing both project_path and group_path' do let(:extra_params) { { project_path: 'foo', group_path: 'bar' } } diff --git a/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb index 8c22e1a1cb6..d88b196cbff 100644 --- a/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb +++ b/spec/graphql/mutations/notes/reposition_image_diff_note_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Mutations::Notes::RepositionImageDiffNote do describe '#resolve' do subject do - mutation.resolve({ note: note, position: new_position }) + mutation.resolve(note: note, position: new_position) end let_it_be(:noteable) { create(:merge_request) } diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index a557e9e04da..c7470f31ad8 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -116,9 +116,9 @@ RSpec.describe ApplicationHelper do Time.use_zone('UTC') { example.run } end - def element(*arguments) + def element(**arguments) @time = Time.zone.parse('2015-07-02 08:23') - element = helper.time_ago_with_tooltip(@time, *arguments) + element = helper.time_ago_with_tooltip(@time, **arguments) Nokogiri::HTML::DocumentFragment.parse(element).first_element_child end diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index 6a5cb73281e..ecb9c98b1bf 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -89,7 +89,7 @@ RSpec.describe ButtonHelper do it 'shows a warning on the dropdown description' do description = element.search('.dropdown-menu-inner-content').first - expect(description.inner_text).to eq "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" + expect(description.inner_text).to eq "You won't be able to pull or push repositories via SSH until you add an SSH key to your profile" end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 34af3ce7e5e..208fba4a7f9 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -104,6 +104,37 @@ RSpec.describe SearchHelper do }) end + it 'includes the users recently viewed issues with the exact same name', :aggregate_failures do + recent_issues = instance_double(::Gitlab::Search::RecentIssues) + expect(::Gitlab::Search::RecentIssues).to receive(:new).with(user: user).and_return(recent_issues) + project1 = create(:project, namespace: user.namespace) + project2 = create(:project, namespace: user.namespace) + issue1 = create(:issue, title: 'issue same_name', project: project1) + issue2 = create(:issue, title: 'issue same_name', project: project2) + + expect(recent_issues).to receive(:search).with('the search term').and_return(Issue.id_in_ordered([issue1.id, issue2.id])) + + results = search_autocomplete_opts("the search term") + + expect(results.count).to eq(2) + + expect(results[0]).to include({ + category: 'Recent issues', + id: issue1.id, + label: 'issue same_name', + url: Gitlab::Routing.url_helpers.project_issue_path(issue1.project, issue1), + avatar_url: '' # This project didn't have an avatar so set this to '' + }) + + expect(results[1]).to include({ + category: 'Recent issues', + id: issue2.id, + label: 'issue same_name', + url: Gitlab::Routing.url_helpers.project_issue_path(issue2.project, issue2), + avatar_url: '' # This project didn't have an avatar so set this to '' + }) + end + it 'includes the users recently viewed merge requests', :aggregate_failures do recent_merge_requests = instance_double(::Gitlab::Search::RecentMergeRequests) expect(::Gitlab::Search::RecentMergeRequests).to receive(:new).with(user: user).and_return(recent_merge_requests) diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb index 05d744d95e2..a99bdcc9a0f 100644 --- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb @@ -15,10 +15,10 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do describe '#run!' do shared_examples_for 'moves the file' do shared_examples_for 'a real run' do - let(:args) { [dry_run: false] } + let(:args) { { dry_run: false } } it 'moves the file to its proper location' do - subject.run!(*args) + subject.run!(**args) expect(File.exist?(path)).to be_falsey expect(File.exist?(new_path)).to be_truthy @@ -28,13 +28,13 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do expect(logger).to receive(:info).with("Looking for orphaned project uploads to clean up...") expect(logger).to receive(:info).with("Did #{action}") - subject.run!(*args) + subject.run!(**args) end end shared_examples_for 'a dry run' do it 'does not move the file' do - subject.run!(*args) + subject.run!(**args) expect(File.exist?(path)).to be_truthy expect(File.exist?(new_path)).to be_falsey @@ -44,30 +44,30 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do expect(logger).to receive(:info).with("Looking for orphaned project uploads to clean up. Dry run...") expect(logger).to receive(:info).with("Can #{action}") - subject.run!(*args) + subject.run!(**args) end end context 'when dry_run is false' do - let(:args) { [dry_run: false] } + let(:args) { { dry_run: false } } it_behaves_like 'a real run' end context 'when dry_run is nil' do - let(:args) { [dry_run: nil] } + let(:args) { { dry_run: nil } } it_behaves_like 'a real run' end context 'when dry_run is true' do - let(:args) { [dry_run: true] } + let(:args) { { dry_run: true } } it_behaves_like 'a dry run' end context 'with dry_run not specified' do - let(:args) { [] } + let(:args) { {} } it_behaves_like 'a dry run' end diff --git a/spec/lib/gitlab/database/batch_count_spec.rb b/spec/lib/gitlab/database/batch_count_spec.rb index a1cc759e011..29688b18e94 100644 --- a/spec/lib/gitlab/database/batch_count_spec.rb +++ b/spec/lib/gitlab/database/batch_count_spec.rb @@ -130,6 +130,16 @@ RSpec.describe Gitlab::Database::BatchCount do expect(described_class.batch_count(model, start: model.minimum(:id), finish: model.maximum(:id))).to eq(5) end + it 'stops counting when finish value is reached' do + stub_const('Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE', 0) + + expect(described_class.batch_count(model, + start: model.minimum(:id), + finish: model.maximum(:id) - 1, # Do not count the last record + batch_size: model.count - 2 # Ensure there are multiple batches + )).to eq(model.count - 1) + end + it "defaults the batch size to #{Gitlab::Database::BatchCounter::DEFAULT_BATCH_SIZE}" do min_id = model.minimum(:id) relation = instance_double(ActiveRecord::Relation) @@ -242,6 +252,19 @@ RSpec.describe Gitlab::Database::BatchCount do expect(described_class.batch_distinct_count(model, column, start: model.minimum(column), finish: model.maximum(column))).to eq(2) end + it 'stops counting when finish value is reached' do + # Create a new unique author that should not be counted + create(:issue) + + stub_const('Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE', 0) + + expect(described_class.batch_distinct_count(model, column, + start: User.minimum(:id), + finish: User.maximum(:id) - 1, # Do not count the newly created issue + batch_size: model.count - 2 # Ensure there are multiple batches + )).to eq(2) + end + it 'counts with User min and max as start and finish' do expect(described_class.batch_distinct_count(model, column, start: User.minimum(:id), finish: User.maximum(:id))).to eq(2) end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index b09bd9dff1b..36f7d89dd0f 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -357,7 +357,7 @@ RSpec.describe Gitlab::GitalyClient::CommitService do end it 'sends an RPC request with the correct payload' do - expect(client.commits_by_message(query, options)).to match_array(wrap_commits(commits)) + expect(client.commits_by_message(query, **options)).to match_array(wrap_commits(commits)) end end diff --git a/spec/migrations/ensure_u2f_registrations_migrated_spec.rb b/spec/migrations/ensure_u2f_registrations_migrated_spec.rb new file mode 100644 index 00000000000..77eab3b829a --- /dev/null +++ b/spec/migrations/ensure_u2f_registrations_migrated_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20201026185514_ensure_u2f_registrations_migrated.rb') + +RSpec.describe EnsureU2fRegistrationsMigrated, schema: 20201022144501 do + let(:u2f_registrations) { table(:u2f_registrations) } + let(:webauthn_registrations) { table(:webauthn_registrations) } + let(:users) { table(:users) } + + let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) } + + before do + create_u2f_registration(1, 'reg1') + create_u2f_registration(2, 'reg2') + webauthn_registrations.create!({ name: 'reg1', u2f_registration_id: 1, credential_xid: '', public_key: '', user_id: user.id }) + end + + it 'correctly migrates u2f registrations previously not migrated' do + expect { migrate! }.to change { webauthn_registrations.count }.from(1).to(2) + end + + it 'migrates all valid u2f registrations depite errors' do + create_u2f_registration(3, 'reg3', 'invalid!') + create_u2f_registration(4, 'reg4') + + expect { migrate! }.to change { webauthn_registrations.count }.from(1).to(3) + end + + def create_u2f_registration(id, name, public_key = nil) + device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5), { key_handle: SecureRandom.random_bytes(255) }) + public_key ||= Base64.strict_encode64(device.origin_public_key_raw) + u2f_registrations.create!({ id: id, + certificate: Base64.strict_encode64(device.cert_raw), + key_handle: U2F.urlsafe_encode64(device.key_handle_raw), + public_key: public_key, + counter: 5, + name: name, + user_id: user.id }) + end +end diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb index 71be0c30f5a..4d8da50f8f0 100644 --- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb +++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb @@ -242,7 +242,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do } expect { authorize_artifacts_with_token_in_headers(artifact_type: :lsif) } - .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(tracking_params) } + .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) } .by(1) end end diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb index 9d422ebbce2..d45e24241b2 100644 --- a/spec/requests/api/go_proxy_spec.rb +++ b/spec/requests/api/go_proxy_spec.rb @@ -428,7 +428,7 @@ RSpec.describe API::GoProxy do context 'with a non-existent project' do def get_resource(user = nil, **params) - get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, params) + get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, **params) end describe 'GET /projects/:id/packages/go/*module_name/@v/list' do @@ -465,7 +465,7 @@ RSpec.describe API::GoProxy do end def get_resource(user = nil, headers: {}, **params) - get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, params), headers: headers + get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, **params), headers: headers end def fmt_pseudo_version(prefix, commit) diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 3c67c15f10a..17b2c7b38e1 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -13,7 +13,7 @@ RSpec.describe EventCreateService do tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today } expect { subject } - .to change { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(tracking_params) } + .to change { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(**tracking_params) } .by(1) end end @@ -386,7 +386,7 @@ RSpec.describe EventCreateService do counter_class = Gitlab::UsageDataCounters::TrackUniqueEvents tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today } - expect { subject }.not_to change { counter_class.count_unique_events(tracking_params) } + expect { subject }.not_to change { counter_class.count_unique_events(**tracking_params) } end end end diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb index 02f80988dd1..90167ffebed 100644 --- a/spec/services/projects/move_access_service_spec.rb +++ b/spec/services/projects/move_access_service_spec.rb @@ -91,7 +91,7 @@ RSpec.describe Projects::MoveAccessService do it 'does not remove remaining memberships' do target_project.add_maintainer(maintainer_user) - subject.execute(project_with_access, options) + subject.execute(project_with_access, **options) expect(project_with_access.project_members.count).not_to eq 0 end @@ -99,7 +99,7 @@ RSpec.describe Projects::MoveAccessService do it 'does not remove remaining group links' do target_project.project_group_links.create!(group: maintainer_group, group_access: Gitlab::Access::MAINTAINER) - subject.execute(project_with_access, options) + subject.execute(project_with_access, **options) expect(project_with_access.project_group_links.count).not_to eq 0 end @@ -107,7 +107,7 @@ RSpec.describe Projects::MoveAccessService do it 'does not remove remaining authorizations' do target_project.add_developer(developer_user) - subject.execute(project_with_access, options) + subject.execute(project_with_access, **options) expect(project_with_access.project_authorizations.count).not_to eq 0 end diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb index e69b4dd4fc7..bd93b80f712 100644 --- a/spec/services/projects/move_deploy_keys_projects_service_spec.rb +++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Projects::MoveDeployKeysProjectsService do it 'does not remove remaining deploy keys projects' do target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first - subject.execute(project_with_deploy_keys, options) + subject.execute(project_with_deploy_keys, **options) expect(project_with_deploy_keys.deploy_keys_projects.count).not_to eq 0 end diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb index b73286fba9a..e3df5fed9cf 100644 --- a/spec/services/projects/move_lfs_objects_projects_service_spec.rb +++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb @@ -48,7 +48,7 @@ RSpec.describe Projects::MoveLfsObjectsProjectsService do it 'does not remove remaining lfs objects' do target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2) - subject.execute(project_with_lfs_objects, options) + subject.execute(project_with_lfs_objects, **options) expect(project_with_lfs_objects.lfs_objects.count).not_to eq 0 end diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb index 7c9f1dd30d2..e381ae7590f 100644 --- a/spec/services/projects/move_notification_settings_service_spec.rb +++ b/spec/services/projects/move_notification_settings_service_spec.rb @@ -49,7 +49,7 @@ RSpec.describe Projects::MoveNotificationSettingsService do let(:options) { { remove_remaining_elements: false } } it 'does not remove remaining notification settings' do - subject.execute(project_with_notifications, options) + subject.execute(project_with_notifications, **options) expect(project_with_notifications.notification_settings.count).not_to eq 0 end diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb index a37b4d807a0..d47b13ca939 100644 --- a/spec/services/projects/move_project_authorizations_service_spec.rb +++ b/spec/services/projects/move_project_authorizations_service_spec.rb @@ -49,7 +49,7 @@ RSpec.describe Projects::MoveProjectAuthorizationsService do target_project.add_maintainer(developer_user) target_project.add_developer(reporter_user) - subject.execute(project_with_users, options) + subject.execute(project_with_users, **options) expect(project_with_users.project_authorizations.count).not_to eq 0 end diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb index 6304eded8d3..1fca96a0367 100644 --- a/spec/services/projects/move_project_group_links_service_spec.rb +++ b/spec/services/projects/move_project_group_links_service_spec.rb @@ -58,7 +58,7 @@ RSpec.describe Projects::MoveProjectGroupLinksService do target_project.project_group_links.create!(group: maintainer_group, group_access: Gitlab::Access::MAINTAINER) target_project.project_group_links.create!(group: developer_group, group_access: Gitlab::Access::DEVELOPER) - subject.execute(project_with_groups, options) + subject.execute(project_with_groups, **options) expect(project_with_groups.project_group_links.count).not_to eq 0 end diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb index f14f00e3866..8fbd0ba3270 100644 --- a/spec/services/projects/move_project_members_service_spec.rb +++ b/spec/services/projects/move_project_members_service_spec.rb @@ -58,7 +58,7 @@ RSpec.describe Projects::MoveProjectMembersService do target_project.add_maintainer(developer_user) target_project.add_developer(reporter_user) - subject.execute(project_with_users, options) + subject.execute(project_with_users, **options) expect(project_with_users.project_members.count).not_to eq 0 end diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index d8ade0fbbda..3e7594bd30f 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Suggestions::ApplyService do position_args = args.slice(:old_path, :new_path, :old_line, :new_line) content_args = args.slice(:from_content, :to_content) - position = build_position(position_args) + position = build_position(**position_args) diff_note = create(:diff_note_on_merge_request, noteable: merge_request, diff --git a/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb new file mode 100644 index 00000000000..d3d2a36147d --- /dev/null +++ b/spec/support/shared_examples/features/wiki/user_git_access_wiki_page_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'User views Git access wiki page' do + let(:wiki_page) { create(:wiki_page, wiki: wiki) } + + before do + sign_in(user) + end + + it 'shows the correct clone URLs', :js do + visit wiki_page_path(wiki, wiki_page) + click_link 'Clone repository' + + expect(page).to have_text("Clone repository #{wiki.full_path}") + + within('.git-clone-holder') do + expect(page).to have_css('#clone-dropdown', text: 'HTTP') + expect(page).to have_field('clone_url', with: wiki.http_url_to_repo) + + click_link 'HTTP' # open the dropdown + click_link 'SSH' # select the dropdown item + + expect(page).to have_css('#clone-dropdown', text: 'SSH') + expect(page).to have_field('clone_url', with: wiki.ssh_url_to_repo) + end + end +end |