diff options
65 files changed, 609 insertions, 149 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index ccc5967ebed..76e8e1b8e65 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -571,3 +571,9 @@ Gitlab/RailsLogger: Exclude: - 'spec/**/*.rb' - 'ee/spec/**/*.rb' + +# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/267606 +FactoryBot/InlineAssociation: + Include: + - 'spec/factories/**/*.rb' + - 'ee/spec/factories/**/*.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7ac92aadeb7..ba6efce0e25 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1278,3 +1278,44 @@ Graphql/IDType: - 'app/graphql/resolvers/snippets_resolver.rb' - 'app/graphql/resolvers/user_merge_requests_resolver.rb' - 'app/graphql/resolvers/user_resolver.rb' + +# Offense count: 86 +# Cop supports --auto-correct. +FactoryBot/InlineAssociation: + Exclude: + - 'ee/spec/factories/analytics/cycle_analytics/group_stages.rb' + - 'ee/spec/factories/ci/reports/security/findings.rb' + - 'ee/spec/factories/ci/reports/security/reports.rb' + - 'ee/spec/factories/geo/event_log.rb' + - 'ee/spec/factories/groups.rb' + - 'ee/spec/factories/merge_request_blocks.rb' + - 'ee/spec/factories/resource_iteration_event.rb' + - 'ee/spec/factories/resource_weight_events.rb' + - 'ee/spec/factories/vulnerabilities/feedback.rb' + - 'spec/factories/atlassian_identities.rb' + - 'spec/factories/audit_events.rb' + - 'spec/factories/design_management/design_at_version.rb' + - 'spec/factories/design_management/designs.rb' + - 'spec/factories/design_management/versions.rb' + - 'spec/factories/events.rb' + - 'spec/factories/git_wiki_commit_details.rb' + - 'spec/factories/gitaly/commit.rb' + - 'spec/factories/go_module_commits.rb' + - 'spec/factories/go_module_versions.rb' + - 'spec/factories/go_modules.rb' + - 'spec/factories/group_group_links.rb' + - 'spec/factories/import_export_uploads.rb' + - 'spec/factories/merge_requests.rb' + - 'spec/factories/notes.rb' + - 'spec/factories/packages.rb' + - 'spec/factories/packages/package_file.rb' + - 'spec/factories/prometheus_alert.rb' + - 'spec/factories/resource_label_events.rb' + - 'spec/factories/resource_milestone_event.rb' + - 'spec/factories/resource_state_event.rb' + - 'spec/factories/sent_notifications.rb' + - 'spec/factories/serverless/domain.rb' + - 'spec/factories/serverless/domain_cluster.rb' + - 'spec/factories/terraform/state.rb' + - 'spec/factories/uploads.rb' + - 'spec/factories/wiki_pages.rb' diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 2f6caffbf84..09f5d5b4dd8 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -1,4 +1,4 @@ -/* eslint-disable no-underscore-dangle, class-methods-use-this */ +/* eslint-disable class-methods-use-this */ import { __ } from '~/locale'; import ListLabel from './label'; import ListAssignee from './assignee'; @@ -34,7 +34,6 @@ const TYPES = { class List { constructor(obj) { this.id = obj.id; - this._uid = this.guid(); this.position = obj.position; this.title = (obj.list_type || obj.listType) === 'backlog' ? __('Open') : obj.title; this.type = obj.list_type || obj.listType; diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index bd1bf17b0c7..1fed1228106 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -1,5 +1,7 @@ import Cookies from 'js-cookie'; import { pick } from 'lodash'; + +import boardListsQuery from 'ee_else_ce/boards/queries/board_lists.query.graphql'; import { __ } from '~/locale'; import { parseBoolean } from '~/lib/utils/common_utils'; import createGqClient, { fetchPolicies } from '~/lib/graphql'; @@ -15,7 +17,6 @@ import { import boardStore from '~/boards/stores/boards_store'; import listsIssuesQuery from '../queries/lists_issues.query.graphql'; -import boardListsQuery from '../queries/board_lists.query.graphql'; import createBoardListMutation from '../queries/board_list_create.mutation.graphql'; import updateBoardListMutation from '../queries/board_list_update.mutation.graphql'; import issueMoveListMutation from '../queries/issue_move_list.mutation.graphql'; @@ -76,10 +77,10 @@ export default { variables, }) .then(({ data }) => { - const { lists } = data[boardType]?.board; + const { lists, hideBacklogList } = data[boardType]?.board; commit(types.RECEIVE_BOARD_LISTS_SUCCESS, formatBoardLists(lists)); - // Backlog list needs to be created if it doesn't exist - if (!lists.nodes.find(l => l.listType === ListType.backlog)) { + // Backlog list needs to be created if it doesn't exist and it's not hidden + if (!lists.nodes.find(l => l.listType === ListType.backlog) && !hideBacklogList) { dispatch('createList', { backlog: true }); } dispatch('showWelcomeList'); diff --git a/app/assets/javascripts/ide/components/file_templates/bar.vue b/app/assets/javascripts/ide/components/file_templates/bar.vue index b6a57d1b6e6..88dca2f0556 100644 --- a/app/assets/javascripts/ide/components/file_templates/bar.vue +++ b/app/assets/javascripts/ide/components/file_templates/bar.vue @@ -1,10 +1,12 @@ <script> +import { GlButton } from '@gitlab/ui'; import { mapActions, mapGetters, mapState } from 'vuex'; import Dropdown from './dropdown.vue'; export default { components: { Dropdown, + GlButton, }, computed: { ...mapGetters(['activeFile']), @@ -65,9 +67,9 @@ export default { @click="selectTemplate" /> <transition name="fade"> - <button v-show="updateSuccess" type="button" class="btn btn-default" @click="undo"> + <gl-button v-show="updateSuccess" category="secondary" variant="default" @click="undo"> {{ __('Undo') }} - </button> + </gl-button> </transition> </div> </template> diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 7c767009de5..56d48e87c18 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -73,11 +73,9 @@ export function initIde(el, options = {}) { * @param {Objects} options - Extra options for the IDE (Used by EE). */ export function startIde(options) { - document.addEventListener('DOMContentLoaded', () => { - const ideElement = document.getElementById('ide'); - if (ideElement) { - resetServiceWorkersPublicPath(); - initIde(ideElement, options); - } - }); + const ideElement = document.getElementById('ide'); + if (ideElement) { + resetServiceWorkersPublicPath(); + initIde(ideElement, options); + } } diff --git a/app/assets/javascripts/snippet/snippet_show.js b/app/assets/javascripts/snippet/snippet_show.js index 2ef532bc6b7..caa76fc9988 100644 --- a/app/assets/javascripts/snippet/snippet_show.js +++ b/app/assets/javascripts/snippet/snippet_show.js @@ -1,13 +1,13 @@ import initNotes from '~/init_notes'; import loadAwardsHandler from '~/awards_handler'; -import { SnippetShowInit } from '~/snippets'; +import SnippetsShow from '~/snippets/components/show.vue'; +import SnippetsAppFactory from '~/snippets'; import ZenMode from '~/zen_mode'; -document.addEventListener('DOMContentLoaded', () => { - SnippetShowInit(); - initNotes(); - loadAwardsHandler(); +SnippetsAppFactory(document.getElementById('js-snippet-view'), SnippetsShow); - // eslint-disable-next-line no-new - new ZenMode(); -}); +initNotes(); +loadAwardsHandler(); + +// eslint-disable-next-line no-new +new ZenMode(); diff --git a/app/assets/javascripts/snippets/index.js b/app/assets/javascripts/snippets/index.js index d3caec42ce7..2c89c206240 100644 --- a/app/assets/javascripts/snippets/index.js +++ b/app/assets/javascripts/snippets/index.js @@ -8,7 +8,7 @@ import { SNIPPET_LEVELS_MAP, SNIPPET_VISIBILITY_PRIVATE } from '~/snippets/const Vue.use(VueApollo); Vue.use(Translate); -function appFactory(el, Component) { +export default function appFactory(el, Component) { if (!el) { return false; } @@ -45,14 +45,6 @@ function appFactory(el, Component) { }); } -export const SnippetShowInit = () => { - import('./components/show.vue') - .then(({ default: SnippetsShow }) => { - appFactory(document.getElementById('js-snippet-view'), SnippetsShow); - }) - .catch(() => {}); -}; - export const SnippetEditInit = () => { import('./components/edit.vue') .then(({ default: SnippetsEdit }) => { diff --git a/app/models/project_repository_storage_move.rb b/app/models/project_repository_storage_move.rb index 2b74d9ccd88..76f428fe925 100644 --- a/app/models/project_repository_storage_move.rb +++ b/app/models/project_repository_storage_move.rb @@ -20,6 +20,10 @@ class ProjectRepositoryStorageMove < ApplicationRecord inclusion: { in: ->(_) { Gitlab.config.repositories.storages.keys } } validate :project_repository_writable, on: :create + default_value_for(:destination_storage_name, allows_nil: false) do + pick_repository_storage + end + state_machine initial: :initial do event :schedule do transition initial: :scheduled @@ -77,6 +81,12 @@ class ProjectRepositoryStorageMove < ApplicationRecord scope :order_created_at_desc, -> { order(created_at: :desc) } scope :with_projects, -> { includes(project: :route) } + class << self + def pick_repository_storage + Project.pick_repository_storage + end + end + private def project_repository_writable diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 9c1ad5bba8f..d71853e11cf 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -283,8 +283,7 @@ class Snippet < ApplicationRecord ::Gitlab::RepositorySizeChecker.new( current_size_proc: -> { repository.size.megabytes }, limit: Gitlab::CurrentSettings.snippet_size_limit, - total_repository_size_excess: nil, - additional_purchased_storage: nil + namespace: nil ) end end diff --git a/app/services/ci/update_build_state_service.rb b/app/services/ci/update_build_state_service.rb index cbb43c3c59e..22a27906700 100644 --- a/app/services/ci/update_build_state_service.rb +++ b/app/services/ci/update_build_state_service.rb @@ -75,6 +75,7 @@ module Ci unless live_chunks_pending? metrics.increment_trace_operation(operation: :finalized) + metrics.observe_migration_duration(pending_state_seconds) end ::Gitlab::Ci::Trace::Checksum.new(build).then do |checksum| @@ -130,7 +131,15 @@ module Ci end def pending_state_outdated? - Time.current - pending_state.created_at > ACCEPT_TIMEOUT + pending_state_duration > ACCEPT_TIMEOUT + end + + def pending_state_duration + Time.current - pending_state.created_at + end + + def pending_state_seconds + pending_state_duration.seconds end def build_state diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 15184c4bae9..fed40b7f119 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -17,10 +17,10 @@ %p #{_('Status')}: #{current_user.two_factor_enabled? ? _('Enabled') : _('Disabled')} - if current_user.two_factor_enabled? - = link_to _('Manage two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-info' + = link_to _('Manage two-factor authentication'), profile_two_factor_auth_path, class: 'gl-button btn btn-info' - else .gl-mb-3 - = link_to _('Enable two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-success', data: { qa_selector: 'enable_2fa_button' } + = link_to _('Enable two-factor authentication'), profile_two_factor_auth_path, class: 'gl-button btn btn-success', data: { qa_selector: 'enable_2fa_button' } %hr - if display_providers_on_profile? diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml index 97f13a55dea..9ec8d694dac 100644 --- a/app/views/profiles/active_sessions/_active_session.html.haml +++ b/app/views/profiles/active_sessions/_active_session.html.haml @@ -30,6 +30,6 @@ = link_to(revoke_session_path(active_session), { data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') }, method: :delete, - class: "btn btn-danger gl-ml-3" }) do + class: "gl-button btn btn-danger gl-ml-3" }) do %span.sr-only= _('Revoke') = _('Revoke') diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml index ff67f92ad07..6805824cebc 100644 --- a/app/views/profiles/chat_names/_chat_name.html.haml +++ b/app/views/profiles/chat_names/_chat_name.html.haml @@ -24,4 +24,4 @@ = _('Never') %td - = link_to _('Remove'), profile_chat_name_path(chat_name), method: :delete, class: 'btn btn-danger float-right', data: { confirm: _('Are you sure you want to revoke this nickname?') } + = link_to _('Remove'), profile_chat_name_path(chat_name), method: :delete, class: 'gl-button btn btn-danger float-right', data: { confirm: _('Are you sure you want to revoke this nickname?') } diff --git a/app/views/profiles/chat_names/new.html.haml b/app/views/profiles/chat_names/new.html.haml index 2134ab2bec6..4651854a551 100644 --- a/app/views/profiles/chat_names/new.html.haml +++ b/app/views/profiles/chat_names/new.html.haml @@ -8,7 +8,7 @@ .actions = form_tag profile_chat_names_path, method: :post do = hidden_field_tag :token, @chat_name_token.token - = submit_tag _("Authorize"), class: "btn btn-success wide float-left" + = submit_tag _("Authorize"), class: "gl-button btn btn-success wide float-left" = form_tag deny_profile_chat_names_path, method: :delete do = hidden_field_tag :token, @chat_name_token.token - = submit_tag _("Deny"), class: "btn btn-danger gl-ml-3" + = submit_tag _("Deny"), class: "gl-button btn btn-danger gl-ml-3" diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index a04ed87801a..0c6dc1a05d8 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -15,7 +15,7 @@ = f.label :email, _('Email'), class: 'label-bold' = f.text_field :email, class: 'form-control', data: { qa_selector: 'email_address_field' } .gl-mt-3 - = f.submit _('Add email address'), class: 'btn btn-success', data: { qa_selector: 'add_email_address_button' } + = f.submit _('Add email address'), class: 'gl-button btn btn-success', data: { qa_selector: 'add_email_address_button' } %hr %h4.gl-mt-0 = _('Linked emails (%{email_count})') % { email_count: @emails.load.size + 1 } @@ -56,8 +56,8 @@ %span.badge.badge-info= s_('Profiles|Notification email') - unless email.confirmed? - confirm_title = "#{email.confirmation_sent_at ? _('Resend confirmation email') : _('Send confirmation email')}" - = link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'btn btn-sm btn-warning gl-ml-3' + = link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'gl-button btn btn-sm btn-warning gl-ml-3' - = link_to profile_email_path(email), data: { confirm: _('Are you sure?'), qa_selector: 'delete_email_link'}, method: :delete, class: 'btn btn-sm btn-danger gl-ml-3' do + = link_to profile_email_path(email), data: { confirm: _('Are you sure?'), qa_selector: 'delete_email_link'}, method: :delete, class: 'gl-button btn btn-sm btn-danger gl-ml-3' do %span.sr-only= _('Remove') = sprite_icon('remove') diff --git a/app/views/profiles/gpg_keys/_form.html.haml b/app/views/profiles/gpg_keys/_form.html.haml index 2fb07adc006..7a7b5802cd8 100644 --- a/app/views/profiles/gpg_keys/_form.html.haml +++ b/app/views/profiles/gpg_keys/_form.html.haml @@ -7,4 +7,4 @@ = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: _("Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'.") .gl-mt-3 - = f.submit s_('Profiles|Add key'), class: "btn btn-success" + = f.submit s_('Profiles|Add key'), class: "gl-button btn btn-success" diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml index f1abafa4149..c851601d4c3 100644 --- a/app/views/profiles/gpg_keys/_key.html.haml +++ b/app/views/profiles/gpg_keys/_key.html.haml @@ -19,9 +19,9 @@ .float-right %span.key-created-at = s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)} - = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "btn btn-danger gl-ml-3" do + = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "gl-button btn btn-danger gl-ml-3" do %span.sr-only= _('Remove') = sprite_icon('remove') - = link_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: "btn btn-danger gl-ml-3" do + = link_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: "gl-button btn btn-danger gl-ml-3" do %span.sr-only= _('Revoke') = _('Revoke') diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index 0a9ea5c4cb3..6a420d7996a 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -24,4 +24,4 @@ %button.btn.btn-success.js-add-ssh-key-validation-confirm-submit= _("Yes, add it") .gl-mt-3 - = f.submit s_('Profiles|Add key'), class: "btn btn-success js-add-ssh-key-validation-original-submit qa-add-key-button" + = f.submit s_('Profiles|Add key'), class: "gl-button btn btn-success js-add-ssh-key-validation-original-submit qa-add-key-button" diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index fe16c2e2f28..1ee5f52e407 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -30,6 +30,6 @@ = f.label :password_confirmation, _('Password confirmation'), class: 'label-bold' = f.password_field :password_confirmation, required: true, class: 'form-control', data: { qa_selector: 'confirm_password_field' } .gl-mt-3.gl-mb-3 - = f.submit _('Save password'), class: "btn btn-success gl-mr-3", data: { qa_selector: 'save_password_button' } + = f.submit _('Save password'), class: "gl-button btn btn-success gl-mr-3", data: { qa_selector: 'save_password_button' } - unless @user.password_automatically_set? = link_to _('I forgot my password'), reset_profile_password_path, method: :put diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index ce60455ab89..f6783528243 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -28,4 +28,4 @@ .col-sm-10 = f.password_field :password_confirmation, required: true, class: 'form-control' .form-actions - = f.submit _('Set new password'), class: 'btn btn-success' + = f.submit _('Set new password'), class: 'gl-button btn btn-success' diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index ea1126fb30f..b8d7e1af005 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -143,4 +143,4 @@ .col-lg-4.profile-settings-sidebar .col-lg-8 .form-group - = f.submit _('Save changes'), class: 'btn btn-success' + = f.submit _('Save changes'), class: 'gl-button btn btn-success' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 86474ea699e..f5fab727a57 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -36,7 +36,7 @@ .form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.") - if @user.avatar? %hr - = link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'btn btn-danger btn-inverted' + = link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'gl-button btn btn-danger btn-inverted' %hr .row @@ -46,7 +46,7 @@ .col-lg-8 = f.fields_for :status, @user.status do |status_form| - emoji_button = button_tag type: :button, - class: 'js-toggle-emoji-menu emoji-menu-toggle-button btn has-tooltip', + class: 'js-toggle-emoji-menu emoji-menu-toggle-button gl-button btn has-tooltip', title: s_("Profiles|Add status emoji") do - if @user.status = emoji_icon @user.status.emoji @@ -56,7 +56,7 @@ = sprite_icon('smile', css_class: 'award-control-icon-super-positive') - reset_message_button = button_tag type: :button, id: 'js-clear-user-status-button', - class: 'clear-user-status btn has-tooltip', + class: 'clear-user-status gl-button btn has-tooltip', title: s_("Profiles|Clear status") do = sprite_icon("close") @@ -78,7 +78,7 @@ -# TODO: might need an entry in user/profile.md to describe some of these settings -# https://gitlab.com/gitlab-org/gitlab-foss/issues/60070 %h5= ("Time zone") - = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown input-lg', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } ) + = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'gl-button btn js-timezone-dropdown input-lg', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } ) %input.hidden{ :type => 'hidden', :id => 'user_timezone', :name => 'user[timezone]', value: @user.timezone } %hr @@ -119,8 +119,8 @@ .help-block = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information") .gl-mt-3.gl-mb-3 - = f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success' - = link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel' + = f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-success' + = link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-cancel' .modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } } .modal-dialog diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml index 40272b6354c..2cb7e022912 100644 --- a/app/views/profiles/two_factor_auths/_codes.html.haml +++ b/app/views/profiles/two_factor_auths/_codes.html.haml @@ -9,5 +9,5 @@ %span.monospace{ data: { qa_selector: 'code_content' } }= code .d-flex - = link_to _('Proceed'), profile_account_path, class: 'btn btn-success gl-mr-3', data: { qa_selector: 'proceed_button' } - = link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default' + = link_to _('Proceed'), profile_account_path, class: 'gl-button btn btn-success gl-mr-3', data: { qa_selector: 'proceed_button' } + = link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'gl-button btn btn-default' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 3e21928e306..ff4ddd4ad69 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -20,7 +20,7 @@ = link_to _('Disable two-factor authentication'), profile_two_factor_auth_path, method: :delete, data: { confirm: webauthn_enabled ? _('Are you sure? This will invalidate your registered applications and U2F / WebAuthn devices.') : _('Are you sure? This will invalidate your registered applications and U2F devices.') }, - class: 'btn btn-danger gl-mr-3' + class: 'gl-button btn btn-danger gl-mr-3' = form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f| = submit_tag _('Regenerate recovery codes'), class: 'btn' @@ -52,7 +52,7 @@ = label_tag :pin_code, _('Pin code'), class: "label-bold" = text_field_tag :pin_code, nil, class: "form-control", required: true, data: { qa_selector: 'pin_code_field' } .gl-mt-3 - = submit_tag _('Register with two-factor app'), class: 'btn btn-success', data: { qa_selector: 'register_2fa_app_button' } + = submit_tag _('Register with two-factor app'), class: 'gl-button btn btn-success', data: { qa_selector: 'register_2fa_app_button' } %hr @@ -109,7 +109,7 @@ %span.gl-text-gray-500 = _("no name set") %td= registration[:created_at].to_date.to_s(:medium) - %td= link_to _('Delete'), registration[:delete_path], method: :delete, class: "btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') } + %td= link_to _('Delete'), registration[:delete_path], method: :delete, class: "gl-button btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') } - else .settings-message.text-center diff --git a/app/views/registrations/welcome.html.haml b/app/views/registrations/welcome.html.haml index 5ad0fbf8fbc..bebcc2152af 100644 --- a/app/views/registrations/welcome.html.haml +++ b/app/views/registrations/welcome.html.haml @@ -22,4 +22,4 @@ - if partial_exists? "registrations/welcome/button" = render "registrations/welcome/button" - else - = f.submit _('Get started!'), class: 'btn-register btn btn-block gl-mb-0 gl-p-3', data: { qa_selector: 'get_started_button' } + = f.submit _('Get started!'), class: 'btn-register gl-button btn btn-block gl-mb-0 gl-p-3', data: { qa_selector: 'get_started_button' } diff --git a/changelogs/unreleased/223236_update_middleman_logo.yml b/changelogs/unreleased/223236_update_middleman_logo.yml new file mode 100644 index 00000000000..9a8a95fbdb0 --- /dev/null +++ b/changelogs/unreleased/223236_update_middleman_logo.yml @@ -0,0 +1,5 @@ +--- +title: Add Middleman Logo for Project Templates +merge_request: 44617 +author: +type: added diff --git a/changelogs/unreleased/automatic_move_storage.yml b/changelogs/unreleased/automatic_move_storage.yml new file mode 100644 index 00000000000..7909d587601 --- /dev/null +++ b/changelogs/unreleased/automatic_move_storage.yml @@ -0,0 +1,5 @@ +--- +title: Allow automatically selecting repository storage on move +merge_request: 45338 +author: +type: changed diff --git a/changelogs/unreleased/cycle-analytics-to-value-stream-analytics-in-university.yml b/changelogs/unreleased/cycle-analytics-to-value-stream-analytics-in-university.yml new file mode 100644 index 00000000000..a090ac909ef --- /dev/null +++ b/changelogs/unreleased/cycle-analytics-to-value-stream-analytics-in-university.yml @@ -0,0 +1,5 @@ +--- +title: Update Cycle Analytics with Value Stream Analytics in University +merge_request: 44244 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/sh-usage-data-pg-system-id.yml b/changelogs/unreleased/sh-usage-data-pg-system-id.yml new file mode 100644 index 00000000000..e8cf62e902d --- /dev/null +++ b/changelogs/unreleased/sh-usage-data-pg-system-id.yml @@ -0,0 +1,5 @@ +--- +title: Include PostgreSQL system identifier in usage ping +merge_request: 44972 +author: +type: added diff --git a/config/feature_flags/development/ci_new_artifact_file_reader.yml b/config/feature_flags/development/ci_new_artifact_file_reader.yml deleted file mode 100644 index 9c0ad7a2d9c..00000000000 --- a/config/feature_flags/development/ci_new_artifact_file_reader.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: ci_new_artifact_file_reader -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40268 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/249588 -group: group::pipeline authoring -type: development -default_enabled: true diff --git a/doc/api/project_repository_storage_moves.md b/doc/api/project_repository_storage_moves.md index 2010fccc624..b490b6235b1 100644 --- a/doc/api/project_repository_storage_moves.md +++ b/doc/api/project_repository_storage_moves.md @@ -194,7 +194,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `project_id` | integer | yes | ID of the project | -| `destination_storage_name` | string | yes | Name of the destination storage shard | +| `destination_storage_name` | string | no | Name of the destination storage shard. If not provided the storage will be selected automatically. | Example request: diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md index a37bec3b43e..15d25d2d1ed 100644 --- a/doc/development/go_guide/index.md +++ b/doc/development/go_guide/index.md @@ -130,7 +130,7 @@ become available, you will be able to share job templates like this Dependencies should be kept to the minimum. The introduction of a new dependency should be argued in the merge request, as per our [Approval Guidelines](../code_review.md#approval-guidelines). Both [License -Management](../../user/compliance/license_compliance/index.md) +Scanning](../../user/compliance/license_compliance/index.md) **(ULTIMATE)** and [Dependency Scanning](../../user/application_security/dependency_scanning/index.md) **(ULTIMATE)** should be activated on all projects to ensure new dependencies diff --git a/doc/development/product_analytics/usage_ping.md b/doc/development/product_analytics/usage_ping.md index c988cabfabe..d482af77d8a 100644 --- a/doc/development/product_analytics/usage_ping.md +++ b/doc/development/product_analytics/usage_ping.md @@ -750,7 +750,8 @@ The following is example content of the Usage Ping payload. }, "database": { "adapter": "postgresql", - "version": "9.6.15" + "version": "9.6.15", + "pg_system_id": 6842684531675334351 }, "avg_cycle_analytics": { "issue": { @@ -910,6 +911,10 @@ The following is example content of the Usage Ping payload. } ``` +## Notable changes + +In GitLab 13.5, `pg_system_id` was added to send the [PostgreSQL system identifier](https://www.2ndquadrant.com/en/blog/support-for-postgresqls-system-identifier-in-barman/). + ## Exporting Usage Ping SQL queries and definitions Two Rake tasks exist to export Usage Ping definitions. diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md index 6603b466251..1a193deca18 100644 --- a/doc/integration/kerberos.md +++ b/doc/integration/kerberos.md @@ -1,3 +1,10 @@ +--- +stage: Create +group: Source Code +info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers" +type: reference, how-to +--- + # Kerberos integration **(STARTER ONLY)** GitLab can integrate with [Kerberos](https://web.mit.edu/kerberos/) as an authentication mechanism. @@ -157,6 +164,13 @@ GitLab users with a linked Kerberos account can also `git pull` and `git push` using Kerberos tokens, i.e., without having to send their password with each operation. +DANGER: **Danger:** +There is a [known issue](https://github.com/curl/curl/issues/1261) with `libcurl` +older than version 7.64.1 wherein it won't reuse connections when negotiating. +This leads to authorization issues when push is larger than `http.postBuffer` +config. Ensure that Git is using at least `libcurl` 7.64.1 to avoid this. To +know the `libcurl` version installed, run `curl-config --version`. + ### HTTP Git access with Kerberos token (passwordless authentication) #### Support for Git before 2.4 diff --git a/doc/university/README.md b/doc/university/README.md index 63252000dd7..6f063e028b5 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -175,10 +175,10 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres 1. [High Availability - Video](https://www.youtube.com/watch?v=36KS808u6bE&index=15&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) 1. [High Availability Documentation](https://about.gitlab.com/solutions/reference-architectures/) -### 3.8 Cycle Analytics +### 3.8 Value Stream Analytics -1. [GitLab Cycle Analytics Overview](https://about.gitlab.com/blog/2016/09/21/cycle-analytics-feature-highlight/) -1. [GitLab Cycle Analytics - Product Page](https://about.gitlab.com/stages-devops-lifecycle/value-stream-analytics/) +1. [GitLab Value Stream Analytics Overview (as of 2016)](https://about.gitlab.com/blog/2016/09/21/cycle-analytics-feature-highlight/) +1. [GitLab Value Stream Analytics - Product Page](https://about.gitlab.com/stages-devops-lifecycle/value-stream-analytics/) ### 3.9. Integrations diff --git a/doc/user/project/wiki/img/wiki_sidebar.png b/doc/user/project/wiki/img/wiki_sidebar.png Binary files differdeleted file mode 100644 index ff39c861a73..00000000000 --- a/doc/user/project/wiki/img/wiki_sidebar.png +++ /dev/null diff --git a/doc/user/project/wiki/img/wiki_sidebar_v13_5.png b/doc/user/project/wiki/img/wiki_sidebar_v13_5.png Binary files differnew file mode 100644 index 00000000000..0f445d61d71 --- /dev/null +++ b/doc/user/project/wiki/img/wiki_sidebar_v13_5.png diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md index 66ecbfc8707..64608b9a915 100644 --- a/doc/user/project/wiki/index.md +++ b/doc/user/project/wiki/index.md @@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated type: reference, how-to --- -# Wiki +# Wiki **(CORE)** A separate system for documentation called Wiki, is built right into each GitLab project. It is enabled by default on all new projects and you can find @@ -130,10 +130,12 @@ be preceded by the slash (`/`) character. ## Viewing a list of all created wiki pages +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17673/) in GitLab 13.5, wiki pages are displayed as a nested tree in the sidebar and pages overview. + Every wiki has a sidebar from which a short list of the created pages can be found. The list is ordered alphabetically. -![Wiki sidebar](img/wiki_sidebar.png) +![Wiki sidebar](img/wiki_sidebar_v13_5.png) If you have many pages, not all will be listed in the sidebar. Click on **View All Pages** to see all of them. diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb index 1a8c4f8d148..38eb74663d3 100644 --- a/lib/api/project_repository_storage_moves.rb +++ b/lib/api/project_repository_storage_moves.rb @@ -69,7 +69,7 @@ module API success Entities::ProjectRepositoryStorageMove end params do - requires :destination_storage_name, type: String, desc: 'The destination storage shard' + optional :destination_storage_name, type: String, desc: 'The destination storage shard' end post ':id/repository_storage_moves' do storage_move = user_project.repository_storage_moves.build( diff --git a/lib/gitlab/ci/artifact_file_reader.rb b/lib/gitlab/ci/artifact_file_reader.rb index 6395a20ca99..b0fad026ec5 100644 --- a/lib/gitlab/ci/artifact_file_reader.rb +++ b/lib/gitlab/ci/artifact_file_reader.rb @@ -45,14 +45,6 @@ module Gitlab end def read_zip_file!(file_path) - if ::Gitlab::Ci::Features.new_artifact_file_reader_enabled?(job.project) - read_with_new_artifact_file_reader(file_path) - else - read_with_legacy_artifact_file_reader(file_path) - end - end - - def read_with_new_artifact_file_reader(file_path) job.artifacts_file.use_open_file do |file| zip_file = Zip::File.new(file, false, true) entry = zip_file.find_entry(file_path) @@ -69,25 +61,6 @@ module Gitlab end end - def read_with_legacy_artifact_file_reader(file_path) - job.artifacts_file.use_file do |archive_path| - Zip::File.open(archive_path) do |zip_file| - entry = zip_file.find_entry(file_path) - unless entry - raise Error, "Path `#{file_path}` does not exist inside the `#{job.name}` artifacts archive!" - end - - if entry.name_is_directory? - raise Error, "Path `#{file_path}` was expected to be a file but it was a directory!" - end - - zip_file.get_input_stream(entry) do |is| - is.read - end - end - end - end - def max_archive_size_in_mb ActiveSupport::NumberHelper.number_to_human_size(MAX_ARCHIVE_SIZE) end diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index 4e5dfa1ebf0..1b58e3ec71a 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -59,10 +59,6 @@ module Gitlab ::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false) end - def self.new_artifact_file_reader_enabled?(project) - ::Feature.enabled?(:ci_new_artifact_file_reader, project, default_enabled: true) - end - def self.one_dimensional_matrix_enabled? ::Feature.enabled?(:one_dimensional_matrix, default_enabled: true) end diff --git a/lib/gitlab/ci/trace/metrics.rb b/lib/gitlab/ci/trace/metrics.rb index 7eb85997605..cdc474d06dc 100644 --- a/lib/gitlab/ci/trace/metrics.rb +++ b/lib/gitlab/ci/trace/metrics.rb @@ -33,6 +33,10 @@ module Gitlab self.class.trace_bytes.increment({}, size.to_i) end + def observe_migration_duration(seconds) + self.class.finalize_histogram.observe({}, seconds.to_f) + end + def self.trace_operations strong_memoize(:trace_operations) do name = :gitlab_ci_trace_operations_total @@ -50,6 +54,17 @@ module Gitlab Gitlab::Metrics.counter(name, comment) end end + + def self.finalize_histogram + strong_memoize(:finalize_histogram) do + name = :gitlab_ci_trace_finalize_duration_seconds + comment = 'Duration of build trace chunks migration to object storage' + buckets = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 10.0, 30.0, 60.0, 300.0] + labels = {} + + ::Gitlab::Metrics.histogram(name, comment, labels, buckets) + end + end end end end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 933adb45c48..45d271a2fd4 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -250,6 +250,12 @@ module Gitlab false end + def self.system_id + row = connection.execute('SELECT system_identifier FROM pg_control_system()').first + + row['system_identifier'] + end + def self.get_write_location(ar_connection) row = ar_connection .select_all("SELECT pg_current_wal_insert_lsn()::text AS location") diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index e1574533fda..a830f949b21 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -53,7 +53,7 @@ module Gitlab ProjectTemplate.new('plainhtml', 'Pages/Plain HTML', _('Everything you need to create a GitLab Pages site using plain HTML.'), 'https://gitlab.com/pages/plain-html'), ProjectTemplate.new('gitbook', 'Pages/GitBook', _('Everything you need to create a GitLab Pages site using GitBook.'), 'https://gitlab.com/pages/gitbook', 'illustrations/logos/gitbook.svg'), ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo', 'illustrations/logos/hexo.svg'), - ProjectTemplate.new('sse_middleman', 'Static Site Editor/Middleman', _('Middleman project with Static Site Editor support'), 'https://gitlab.com/gitlab-org/project-templates/static-site-editor-middleman'), + ProjectTemplate.new('sse_middleman', 'Static Site Editor/Middleman', _('Middleman project with Static Site Editor support'), 'https://gitlab.com/gitlab-org/project-templates/static-site-editor-middleman', 'illustrations/logos/middleman.svg'), ProjectTemplate.new('gitpod_spring_petclinic', 'Gitpod/Spring Petclinic', _('A Gitpod configured Webapplication in Spring and Java'), 'https://gitlab.com/gitlab-org/project-templates/gitpod-spring-petclinic', 'illustrations/logos/gitpod.svg'), ProjectTemplate.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo', 'illustrations/logos/netlify.svg'), ProjectTemplate.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll', 'illustrations/logos/netlify.svg'), diff --git a/lib/gitlab/repository_size_checker.rb b/lib/gitlab/repository_size_checker.rb index cfc139575ad..03d9f961dd9 100644 --- a/lib/gitlab/repository_size_checker.rb +++ b/lib/gitlab/repository_size_checker.rb @@ -3,14 +3,13 @@ module Gitlab # Centralized class for repository size related calculations. class RepositorySizeChecker - attr_reader :limit, :total_repository_size_excess, :additional_purchased_storage + attr_reader :limit # @param current_size_proc [Proc] returns repository size in bytes - def initialize(current_size_proc:, limit:, total_repository_size_excess:, additional_purchased_storage:, enabled: true) + def initialize(current_size_proc:, limit:, namespace:, enabled: true) @current_size_proc = current_size_proc @limit = limit - @total_repository_size_excess = total_repository_size_excess.to_i - @additional_purchased_storage = additional_purchased_storage.to_i + @namespace = namespace @enabled = enabled && limit != 0 end @@ -44,6 +43,10 @@ module Gitlab def error_message @error_message_object ||= ::Gitlab::RepositorySizeErrorMessage.new(self) end + + private + + attr_reader :namespace end end diff --git a/lib/gitlab/repository_size_error_message.rb b/lib/gitlab/repository_size_error_message.rb index a1969d87a76..556190453de 100644 --- a/lib/gitlab/repository_size_error_message.rb +++ b/lib/gitlab/repository_size_error_message.rb @@ -4,7 +4,7 @@ module Gitlab class RepositorySizeErrorMessage include ActiveSupport::NumberHelper - delegate :current_size, :limit, :total_repository_size_excess, :additional_purchased_storage, :exceeded_size, to: :@checker + delegate :current_size, :limit, :exceeded_size, to: :@checker # @param checher [RepositorySizeChecker] def initialize(checker) diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index af470e4028d..68f24559b1f 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -303,7 +303,8 @@ module Gitlab }, database: { adapter: alt_usage_data { Gitlab::Database.adapter_name }, - version: alt_usage_data { Gitlab::Database.version } + version: alt_usage_data { Gitlab::Database.version }, + pg_system_id: alt_usage_data { Gitlab::Database.system_id } }, mail: { smtp_server: alt_usage_data { ActionMailer::Base.smtp_settings[:address] } diff --git a/rubocop/cop/migration/add_limit_to_text_columns.rb b/rubocop/cop/migration/add_limit_to_text_columns.rb index b578e73f19e..b2e37ad5137 100644 --- a/rubocop/cop/migration/add_limit_to_text_columns.rb +++ b/rubocop/cop/migration/add_limit_to_text_columns.rb @@ -6,6 +6,10 @@ module RuboCop module Cop module Migration # Cop that enforces always adding a limit on text columns + # + # Text columns starting with `encrypted_` are very likely used + # by `attr_encrypted` which controls the text length. Those columns + # should not add a text limit. class AddLimitToTextColumns < RuboCop::Cop::Cop include MigrationHelpers @@ -102,6 +106,8 @@ module RuboCop # Check if there is an `add_text_limit` call for the provided # table and attribute name def text_limit_missing?(node, table_name, attribute_name) + return false if encrypted_attribute_name?(attribute_name) + limit_found = false node.each_descendant(:send) do |send_node| @@ -118,6 +124,10 @@ module RuboCop !limit_found end + + def encrypted_attribute_name?(attribute_name) + attribute_name.to_s.start_with?('encrypted_') + end end end end diff --git a/rubocop/cop/rspec/factory_bot/inline_association.rb b/rubocop/cop/rspec/factory_bot/inline_association.rb new file mode 100644 index 00000000000..1c2b8b55b46 --- /dev/null +++ b/rubocop/cop/rspec/factory_bot/inline_association.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + module FactoryBot + # This cop encourages the use of inline associations in FactoryBot. + # The explicit use of `create` and `build` is discouraged. + # + # See https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#inline-definition + # + # @example + # + # Context: + # + # Factory.define do + # factory :project, class: 'Project' + # # EXAMPLE below + # end + # end + # + # # bad + # creator { create(:user) } + # creator { create(:user, :admin) } + # creator { build(:user) } + # creator { FactoryBot.build(:user) } + # creator { ::FactoryBot.build(:user) } + # add_attribute(:creator) { build(:user) } + # + # # good + # creator { association(:user) } + # creator { association(:user, :admin) } + # add_attribute(:creator) { association(:user) } + # + # # Accepted + # after(:build) do |instance| + # instance.creator = create(:user) + # end + # + # initialize_with do + # create(:project) + # end + # + # creator_id { create(:user).id } + # + class InlineAssociation < RuboCop::Cop::Cop + MSG = 'Prefer inline `association` over `%{type}`. ' \ + 'See https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#factories' + + REPLACEMENT = 'association' + + def_node_matcher :create_or_build, <<~PATTERN + ( + send + ${ nil? (const { nil? (cbase) } :FactoryBot) } + ${ :create :build } + (sym _) + ... + ) + PATTERN + + def_node_matcher :association_definition, <<~PATTERN + (block + { + (send nil? $_) + (send nil? :add_attribute (sym $_)) + } + ... + ) + PATTERN + + def_node_matcher :chained_call?, <<~PATTERN + (send _ _) + PATTERN + + SKIP_NAMES = %i[initialize_with].to_set.freeze + + def on_send(node) + _receiver, type = create_or_build(node) + return unless type + return if chained_call?(node.parent) + return unless inside_assocation_definition?(node) + + add_offense(node, message: format(MSG, type: type)) + end + + def autocorrect(node) + lambda do |corrector| + receiver, type = create_or_build(node) + receiver = "#{receiver.source}." if receiver + expression = "#{receiver}#{type}" + replacement = node.source.sub(expression, REPLACEMENT) + corrector.replace(node.source_range, replacement) + end + end + + private + + def inside_assocation_definition?(node) + node.each_ancestor(:block).any? do |parent| + name = association_definition(parent) + name && !SKIP_NAMES.include?(name) + end + end + end + end + end + end +end diff --git a/spec/factories/project_repository_storage_moves.rb b/spec/factories/project_repository_storage_moves.rb index 69fb3af45e6..c0068de5f58 100644 --- a/spec/factories/project_repository_storage_moves.rb +++ b/spec/factories/project_repository_storage_moves.rb @@ -5,7 +5,6 @@ FactoryBot.define do project source_storage_name { 'default' } - destination_storage_name { 'default' } trait :scheduled do state { ProjectRepositoryStorageMove.state_machines[:state].states[:scheduled].value } diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index fb882aaa8d6..78e70161121 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -13,7 +13,7 @@ import actions, { gqlClient } from '~/boards/stores/actions'; import * as types from '~/boards/stores/mutation_types'; import { inactiveId, ListType } from '~/boards/constants'; import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql'; -import { fullBoardId, formatListIssues } from '~/boards/boards_util'; +import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util'; const expectNotImplemented = action => { it('is not implemented', () => { @@ -78,6 +78,80 @@ describe('setActiveId', () => { }); }); +describe('fetchLists', () => { + const state = { + endpoints: { + fullPath: 'gitlab-org', + boardId: 1, + }, + filterParams: {}, + boardType: 'group', + }; + + let queryResponse = { + data: { + group: { + board: { + hideBacklogList: true, + lists: { + nodes: [mockLists[1]], + }, + }, + }, + }, + }; + + const formattedLists = formatBoardLists(queryResponse.data.group.board.lists); + + it('should commit mutations RECEIVE_BOARD_LISTS_SUCCESS on success', done => { + jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse); + + testAction( + actions.fetchLists, + {}, + state, + [ + { + type: types.RECEIVE_BOARD_LISTS_SUCCESS, + payload: formattedLists, + }, + ], + [{ type: 'showWelcomeList' }], + done, + ); + }); + + it('dispatch createList action when backlog list does not exist and is not hidden', done => { + queryResponse = { + data: { + group: { + board: { + hideBacklogList: false, + lists: { + nodes: [mockLists[1]], + }, + }, + }, + }, + }; + jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse); + + testAction( + actions.fetchLists, + {}, + state, + [ + { + type: types.RECEIVE_BOARD_LISTS_SUCCESS, + payload: formattedLists, + }, + ], + [{ type: 'createList', payload: { backlog: true } }, { type: 'showWelcomeList' }], + done, + ); + }); +}); + describe('showWelcomeList', () => { it('should dispatch addList action', done => { const state = { diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb index 83a37655ea9..e982f0eb015 100644 --- a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb +++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb @@ -18,17 +18,6 @@ RSpec.describe Gitlab::Ci::ArtifactFileReader do expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') end - context 'when FF ci_new_artifact_file_reader is disabled' do - before do - stub_feature_flags(ci_new_artifact_file_reader: false) - end - - it 'returns the content at the path' do - is_expected.to be_present - expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') - end - end - context 'when path does not exist' do let(:path) { 'file/does/not/exist.txt' } let(:expected_error) do diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 644dcab8a66..3175040167b 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -39,6 +39,12 @@ RSpec.describe Gitlab::Database do end end + describe '.system_id' do + it 'returns the PostgreSQL system identifier' do + expect(described_class.system_id).to be_an_instance_of(Integer) + end + end + describe '.postgresql?' do subject { described_class.postgresql? } diff --git a/spec/lib/gitlab/repository_size_checker_spec.rb b/spec/lib/gitlab/repository_size_checker_spec.rb index f885fefb31e..bd030d81d97 100644 --- a/spec/lib/gitlab/repository_size_checker_spec.rb +++ b/spec/lib/gitlab/repository_size_checker_spec.rb @@ -3,16 +3,16 @@ require 'spec_helper' RSpec.describe Gitlab::RepositorySizeChecker do + let_it_be(:namespace) { nil } let(:current_size) { 0 } let(:limit) { 50 } let(:enabled) { true } subject do described_class.new( - current_size_proc: -> { current_size }, - limit: limit, - total_repository_size_excess: 0, - additional_purchased_storage: 0, + current_size_proc: -> { current_size.megabytes }, + limit: limit.megabytes, + namespace: namespace, enabled: enabled ) end @@ -20,7 +20,7 @@ RSpec.describe Gitlab::RepositorySizeChecker do describe '#enabled?' do context 'when enabled' do it 'returns true' do - expect(subject.enabled?).to be_truthy + expect(subject.enabled?).to eq(true) end end @@ -28,7 +28,7 @@ RSpec.describe Gitlab::RepositorySizeChecker do let(:limit) { 0 } it 'returns false' do - expect(subject.enabled?).to be_falsey + expect(subject.enabled?).to eq(false) end end end @@ -37,11 +37,11 @@ RSpec.describe Gitlab::RepositorySizeChecker do let(:current_size) { 49 } it 'returns true when changes go over' do - expect(subject.changes_will_exceed_size_limit?(2)).to be_truthy + expect(subject.changes_will_exceed_size_limit?(2.megabytes)).to eq(true) end it 'returns false when changes do not go over' do - expect(subject.changes_will_exceed_size_limit?(1)).to be_falsey + expect(subject.changes_will_exceed_size_limit?(1.megabytes)).to eq(false) end end diff --git a/spec/lib/gitlab/repository_size_error_message_spec.rb b/spec/lib/gitlab/repository_size_error_message_spec.rb index 6cf1b8c9696..53b5ed5518f 100644 --- a/spec/lib/gitlab/repository_size_error_message_spec.rb +++ b/spec/lib/gitlab/repository_size_error_message_spec.rb @@ -3,11 +3,11 @@ require 'spec_helper' RSpec.describe Gitlab::RepositorySizeErrorMessage do + let_it_be(:namespace) { build(:namespace) } let(:checker) do Gitlab::RepositorySizeChecker.new( current_size_proc: -> { 15.megabytes }, - total_repository_size_excess: 0, - additional_purchased_storage: 0, + namespace: namespace, limit: 10.megabytes ) end @@ -15,6 +15,10 @@ RSpec.describe Gitlab::RepositorySizeErrorMessage do let(:message) { checker.error_message } let(:base_message) { 'because this repository has exceeded its size limit of 10 MB by 5 MB' } + before do + allow(namespace).to receive(:total_repository_size_excess).and_return(0) + end + describe 'error messages' do describe '#commit_error' do it 'returns the correct message' do diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 7792fd99176..7d35c03e894 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -723,6 +723,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do expect(subject[:git][:version]).to eq(Gitlab::Git.version) expect(subject[:database][:adapter]).to eq(Gitlab::Database.adapter_name) expect(subject[:database][:version]).to eq(Gitlab::Database.version) + expect(subject[:database][:pg_system_id]).to eq(Gitlab::Database.system_id) expect(subject[:mail][:smtp_server]).to eq(ActionMailer::Base.smtp_settings[:address]) expect(subject[:gitaly][:version]).to be_present expect(subject[:gitaly][:servers]).to be >= 1 diff --git a/spec/models/project_repository_storage_move_spec.rb b/spec/models/project_repository_storage_move_spec.rb index 3e679c8af4d..d32867efb39 100644 --- a/spec/models/project_repository_storage_move_spec.rb +++ b/spec/models/project_repository_storage_move_spec.rb @@ -43,6 +43,18 @@ RSpec.describe ProjectRepositoryStorageMove, type: :model do end end + describe 'defaults' do + context 'destination_storage_name' do + subject { build(:project_repository_storage_move) } + + it 'picks storage from ApplicationSetting' do + expect(Gitlab::CurrentSettings).to receive(:pick_repository_storage).and_return('picked').at_least(:once) + + expect(subject.destination_storage_name).to eq('picked') + end + end + end + describe 'state transitions' do let(:project) { create(:project) } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index b031514d3a6..d74f5faab7f 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -666,8 +666,7 @@ RSpec.describe Snippet do let(:checker) { subject.repository_size_checker } let(:current_size) { 60 } - let(:total_repository_size_excess) { 0 } - let(:additional_purchased_storage) { 0 } + let(:namespace) { nil } before do allow(subject.repository).to receive(:size).and_return(current_size) diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb index 4c9e058ef13..ecf4c75b52f 100644 --- a/spec/requests/api/project_repository_storage_moves_spec.rb +++ b/spec/requests/api/project_repository_storage_moves_spec.rb @@ -145,10 +145,17 @@ RSpec.describe API::ProjectRepositoryStorageMoves do context 'destination_storage_name is missing' do let(:destination_storage_name) { nil } - it 'returns a validation error' do + it 'schedules a project repository storage move' do create_project_repository_storage_move - expect(response).to have_gitlab_http_status(:bad_request) + storage_move = project.repository_storage_moves.last + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/project_repository_storage_move') + expect(json_response['id']).to eq(storage_move.id) + expect(json_response['state']).to eq('scheduled') + expect(json_response['source_storage_name']).to eq('default') + expect(json_response['destination_storage_name']).to be_present end end end diff --git a/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb b/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb index 5f0ca419548..0bea7bd7a0c 100644 --- a/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb +++ b/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb @@ -129,6 +129,28 @@ RSpec.describe RuboCop::Cop::Migration::AddLimitToTextColumns, type: :rubocop do end end + context 'when text columns are used for encryption' do + it 'registers no offenses' do + expect_no_offenses(<<~RUBY) + class TestTextLimits < ActiveRecord::Migration[6.0] + DOWNTIME = false + disable_ddl_transaction! + + def up + create_table :test_text_limits, id: false do |t| + t.integer :test_id, null: false + t.text :encrypted_name + end + + add_column :encrypted_test_text_limits, :encrypted_email, :text + add_column_with_default :encrypted_test_text_limits, :encrypted_role, :text, default: 'default' + change_column_type_concurrently :encrypted_test_text_limits, :encrypted_test_id, :text + end + end + RUBY + end + end + context 'on down' do it 'registers no offense' do expect_no_offenses(<<~RUBY) diff --git a/spec/rubocop/cop/rspec/factory_bot/inline_association_spec.rb b/spec/rubocop/cop/rspec/factory_bot/inline_association_spec.rb new file mode 100644 index 00000000000..70dbe086127 --- /dev/null +++ b/spec/rubocop/cop/rspec/factory_bot/inline_association_spec.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' +require 'rubocop' + +require_relative '../../../../../rubocop/cop/rspec/factory_bot/inline_association' + +RSpec.describe RuboCop::Cop::RSpec::FactoryBot::InlineAssociation, type: :rubocop do + include CopHelper + + subject(:cop) { described_class.new } + + shared_examples 'offense' do |code_snippet, autocorrected| + # We allow `create` or `FactoryBot.create` or `::FactoryBot.create` + let(:type) { code_snippet[/^(?:::)?(?:FactoryBot\.)?(\w+)/, 1] } + let(:offense_marker) { '^' * code_snippet.size } + let(:offense_msg) { msg(type) } + let(:offense) { "#{offense_marker} #{offense_msg}" } + let(:pristine_source) { source.sub(offense, '') } + let(:source) do + <<~RUBY + FactoryBot.define do + factory :project do + attribute { #{code_snippet} } + #{offense} + end + end + RUBY + end + + it 'registers an offense' do + expect_offense(source) + end + + it 'autocorrects the source' do + corrected = autocorrect_source(pristine_source) + + expect(corrected).not_to include(code_snippet) + expect(corrected).to include(autocorrected) + end + end + + shared_examples 'no offense' do |code_snippet| + first_line = code_snippet.lines.first.chomp + + context "for `#{first_line}`" do + it 'does not register any offenses' do + expect_no_offenses <<~RUBY + FactoryBot.define do + factory :project do + #{code_snippet} + end + end + RUBY + end + end + end + + context 'offenses' do + using RSpec::Parameterized::TableSyntax + + where(:code_snippet, :autocorrected) do + # create + 'create(:user)' | 'association(:user)' + 'FactoryBot.create(:user)' | 'association(:user)' + '::FactoryBot.create(:user)' | 'association(:user)' + 'create(:user, :admin)' | 'association(:user, :admin)' + 'create(:user, name: "any")' | 'association(:user, name: "any")' + # build + 'build(:user)' | 'association(:user)' + 'FactoryBot.build(:user)' | 'association(:user)' + '::FactoryBot.build(:user)' | 'association(:user)' + 'build(:user, :admin)' | 'association(:user, :admin)' + 'build(:user, name: "any")' | 'association(:user, name: "any")' + end + + with_them do + include_examples 'offense', params[:code_snippet], params[:autocorrected] + end + + it 'recognizes `add_attribute`' do + expect_offense <<~RUBY + FactoryBot.define do + factory :project, class: 'Project' do + add_attribute(:method) { create(:user) } + ^^^^^^^^^^^^^ #{msg(:create)} + end + end + RUBY + end + + it 'recognizes `transient` attributes' do + expect_offense <<~RUBY + FactoryBot.define do + factory :project, class: 'Project' do + transient do + creator { create(:user) } + ^^^^^^^^^^^^^ #{msg(:create)} + end + end + end + RUBY + end + end + + context 'no offenses' do + include_examples 'no offense', 'association(:user)' + include_examples 'no offense', 'association(:user, :admin)' + include_examples 'no offense', 'association(:user, name: "any")' + + include_examples 'no offense', <<~RUBY + after(:build) do |object| + object.user = create(:user) + end + RUBY + + include_examples 'no offense', <<~RUBY + initialize_with do + create(:user) + end + RUBY + + include_examples 'no offense', <<~RUBY + user_id { create(:user).id } + RUBY + end + + def msg(type) + format(described_class::MSG, type: type) + end +end diff --git a/spec/services/ci/update_build_state_service_spec.rb b/spec/services/ci/update_build_state_service_spec.rb index aa1de368154..2545909bf56 100644 --- a/spec/services/ci/update_build_state_service_spec.rb +++ b/spec/services/ci/update_build_state_service_spec.rb @@ -131,6 +131,18 @@ RSpec.describe Ci::UpdateBuildStateService do .with(operation: :finalized) end + it 'records migration duration in a histogram' do + freeze_time do + create(:ci_build_pending_state, build: build, created_at: 0.5.seconds.ago) + + execute_with_stubbed_metrics! + end + + expect(metrics) + .to have_received(:observe_migration_duration) + .with(0.5) + end + context 'when trace checksum is not valid' do it 'increments invalid trace metric' do execute_with_stubbed_metrics! diff --git a/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb index 432775208b6..bb909ffe82a 100644 --- a/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/repository_size_checker_shared_examples.rb @@ -29,7 +29,7 @@ RSpec.shared_examples 'checker size exceeded' do let(:current_size) { 51 } it 'returns zero' do - expect(subject.exceeded_size).to eq(1) + expect(subject.exceeded_size).to eq(1.megabytes) end end @@ -37,7 +37,7 @@ RSpec.shared_examples 'checker size exceeded' do let(:current_size) { 50 } it 'returns zero' do - expect(subject.exceeded_size(1)).to eq(1) + expect(subject.exceeded_size(1.megabytes)).to eq(1.megabytes) end end @@ -45,7 +45,7 @@ RSpec.shared_examples 'checker size exceeded' do let(:current_size) { 49 } it 'returns zero' do - expect(subject.exceeded_size(1)).to eq(0) + expect(subject.exceeded_size(1.megabytes)).to eq(0) end end end diff --git a/spec/support/shared_examples/models/snippet_shared_examples.rb b/spec/support/shared_examples/models/snippet_shared_examples.rb index 79f2d94ab83..a8fdf9bb81e 100644 --- a/spec/support/shared_examples/models/snippet_shared_examples.rb +++ b/spec/support/shared_examples/models/snippet_shared_examples.rb @@ -4,8 +4,7 @@ RSpec.shared_examples 'size checker for snippet' do |action| it 'sets up size checker', :aggregate_failures do expect(checker.current_size).to eq(current_size.megabytes) expect(checker.limit).to eq(Gitlab::CurrentSettings.snippet_size_limit) - expect(checker.total_repository_size_excess).to eq(total_repository_size_excess) - expect(checker.additional_purchased_storage).to eq(additional_purchased_storage) expect(checker.enabled?).to eq(true) + expect(checker.instance_variable_get(:@namespace)).to eq(namespace) end end |