Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-19 12:09:20 +00:00
parent 93e4425400
commit 6559f0ee67
65 changed files with 609 additions and 149 deletions

View File

@ -571,3 +571,9 @@ Gitlab/RailsLogger:
Exclude: Exclude:
- 'spec/**/*.rb' - 'spec/**/*.rb'
- 'ee/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'

View File

@ -1278,3 +1278,44 @@ Graphql/IDType:
- 'app/graphql/resolvers/snippets_resolver.rb' - 'app/graphql/resolvers/snippets_resolver.rb'
- 'app/graphql/resolvers/user_merge_requests_resolver.rb' - 'app/graphql/resolvers/user_merge_requests_resolver.rb'
- 'app/graphql/resolvers/user_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'

View File

@ -1,4 +1,4 @@
/* eslint-disable no-underscore-dangle, class-methods-use-this */ /* eslint-disable class-methods-use-this */
import { __ } from '~/locale'; import { __ } from '~/locale';
import ListLabel from './label'; import ListLabel from './label';
import ListAssignee from './assignee'; import ListAssignee from './assignee';
@ -34,7 +34,6 @@ const TYPES = {
class List { class List {
constructor(obj) { constructor(obj) {
this.id = obj.id; this.id = obj.id;
this._uid = this.guid();
this.position = obj.position; this.position = obj.position;
this.title = (obj.list_type || obj.listType) === 'backlog' ? __('Open') : obj.title; this.title = (obj.list_type || obj.listType) === 'backlog' ? __('Open') : obj.title;
this.type = obj.list_type || obj.listType; this.type = obj.list_type || obj.listType;

View File

@ -1,5 +1,7 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { pick } from 'lodash'; import { pick } from 'lodash';
import boardListsQuery from 'ee_else_ce/boards/queries/board_lists.query.graphql';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql'; import createGqClient, { fetchPolicies } from '~/lib/graphql';
@ -15,7 +17,6 @@ import {
import boardStore from '~/boards/stores/boards_store'; import boardStore from '~/boards/stores/boards_store';
import listsIssuesQuery from '../queries/lists_issues.query.graphql'; 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 createBoardListMutation from '../queries/board_list_create.mutation.graphql';
import updateBoardListMutation from '../queries/board_list_update.mutation.graphql'; import updateBoardListMutation from '../queries/board_list_update.mutation.graphql';
import issueMoveListMutation from '../queries/issue_move_list.mutation.graphql'; import issueMoveListMutation from '../queries/issue_move_list.mutation.graphql';
@ -76,10 +77,10 @@ export default {
variables, variables,
}) })
.then(({ data }) => { .then(({ data }) => {
const { lists } = data[boardType]?.board; const { lists, hideBacklogList } = data[boardType]?.board;
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, formatBoardLists(lists)); commit(types.RECEIVE_BOARD_LISTS_SUCCESS, formatBoardLists(lists));
// Backlog list needs to be created if it doesn't exist // 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)) { if (!lists.nodes.find(l => l.listType === ListType.backlog) && !hideBacklogList) {
dispatch('createList', { backlog: true }); dispatch('createList', { backlog: true });
} }
dispatch('showWelcomeList'); dispatch('showWelcomeList');

View File

@ -1,10 +1,12 @@
<script> <script>
import { GlButton } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import Dropdown from './dropdown.vue'; import Dropdown from './dropdown.vue';
export default { export default {
components: { components: {
Dropdown, Dropdown,
GlButton,
}, },
computed: { computed: {
...mapGetters(['activeFile']), ...mapGetters(['activeFile']),
@ -65,9 +67,9 @@ export default {
@click="selectTemplate" @click="selectTemplate"
/> />
<transition name="fade"> <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') }} {{ __('Undo') }}
</button> </gl-button>
</transition> </transition>
</div> </div>
</template> </template>

View File

@ -73,11 +73,9 @@ export function initIde(el, options = {}) {
* @param {Objects} options - Extra options for the IDE (Used by EE). * @param {Objects} options - Extra options for the IDE (Used by EE).
*/ */
export function startIde(options) { export function startIde(options) {
document.addEventListener('DOMContentLoaded', () => { const ideElement = document.getElementById('ide');
const ideElement = document.getElementById('ide'); if (ideElement) {
if (ideElement) { resetServiceWorkersPublicPath();
resetServiceWorkersPublicPath(); initIde(ideElement, options);
initIde(ideElement, options); }
}
});
} }

View File

@ -1,13 +1,13 @@
import initNotes from '~/init_notes'; import initNotes from '~/init_notes';
import loadAwardsHandler from '~/awards_handler'; 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'; import ZenMode from '~/zen_mode';
document.addEventListener('DOMContentLoaded', () => { SnippetsAppFactory(document.getElementById('js-snippet-view'), SnippetsShow);
SnippetShowInit();
initNotes();
loadAwardsHandler();
// eslint-disable-next-line no-new initNotes();
new ZenMode(); loadAwardsHandler();
});
// eslint-disable-next-line no-new
new ZenMode();

View File

@ -8,7 +8,7 @@ import { SNIPPET_LEVELS_MAP, SNIPPET_VISIBILITY_PRIVATE } from '~/snippets/const
Vue.use(VueApollo); Vue.use(VueApollo);
Vue.use(Translate); Vue.use(Translate);
function appFactory(el, Component) { export default function appFactory(el, Component) {
if (!el) { if (!el) {
return false; 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 = () => { export const SnippetEditInit = () => {
import('./components/edit.vue') import('./components/edit.vue')
.then(({ default: SnippetsEdit }) => { .then(({ default: SnippetsEdit }) => {

View File

@ -20,6 +20,10 @@ class ProjectRepositoryStorageMove < ApplicationRecord
inclusion: { in: ->(_) { Gitlab.config.repositories.storages.keys } } inclusion: { in: ->(_) { Gitlab.config.repositories.storages.keys } }
validate :project_repository_writable, on: :create 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 state_machine initial: :initial do
event :schedule do event :schedule do
transition initial: :scheduled transition initial: :scheduled
@ -77,6 +81,12 @@ class ProjectRepositoryStorageMove < ApplicationRecord
scope :order_created_at_desc, -> { order(created_at: :desc) } scope :order_created_at_desc, -> { order(created_at: :desc) }
scope :with_projects, -> { includes(project: :route) } scope :with_projects, -> { includes(project: :route) }
class << self
def pick_repository_storage
Project.pick_repository_storage
end
end
private private
def project_repository_writable def project_repository_writable

View File

@ -283,8 +283,7 @@ class Snippet < ApplicationRecord
::Gitlab::RepositorySizeChecker.new( ::Gitlab::RepositorySizeChecker.new(
current_size_proc: -> { repository.size.megabytes }, current_size_proc: -> { repository.size.megabytes },
limit: Gitlab::CurrentSettings.snippet_size_limit, limit: Gitlab::CurrentSettings.snippet_size_limit,
total_repository_size_excess: nil, namespace: nil
additional_purchased_storage: nil
) )
end end
end end

View File

@ -75,6 +75,7 @@ module Ci
unless live_chunks_pending? unless live_chunks_pending?
metrics.increment_trace_operation(operation: :finalized) metrics.increment_trace_operation(operation: :finalized)
metrics.observe_migration_duration(pending_state_seconds)
end end
::Gitlab::Ci::Trace::Checksum.new(build).then do |checksum| ::Gitlab::Ci::Trace::Checksum.new(build).then do |checksum|
@ -130,7 +131,15 @@ module Ci
end end
def pending_state_outdated? 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 end
def build_state def build_state

View File

@ -17,10 +17,10 @@
%p %p
#{_('Status')}: #{current_user.two_factor_enabled? ? _('Enabled') : _('Disabled')} #{_('Status')}: #{current_user.two_factor_enabled? ? _('Enabled') : _('Disabled')}
- if current_user.two_factor_enabled? - 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 - else
.gl-mb-3 .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 %hr
- if display_providers_on_profile? - if display_providers_on_profile?

View File

@ -30,6 +30,6 @@
= link_to(revoke_session_path(active_session), = 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.') }, { data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') },
method: :delete, method: :delete,
class: "btn btn-danger gl-ml-3" }) do class: "gl-button btn btn-danger gl-ml-3" }) do
%span.sr-only= _('Revoke') %span.sr-only= _('Revoke')
= _('Revoke') = _('Revoke')

View File

@ -24,4 +24,4 @@
= _('Never') = _('Never')
%td %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?') }

View File

@ -8,7 +8,7 @@
.actions .actions
= form_tag profile_chat_names_path, method: :post do = form_tag profile_chat_names_path, method: :post do
= hidden_field_tag :token, @chat_name_token.token = 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 = form_tag deny_profile_chat_names_path, method: :delete do
= hidden_field_tag :token, @chat_name_token.token = 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"

View File

@ -15,7 +15,7 @@
= f.label :email, _('Email'), class: 'label-bold' = f.label :email, _('Email'), class: 'label-bold'
= f.text_field :email, class: 'form-control', data: { qa_selector: 'email_address_field' } = f.text_field :email, class: 'form-control', data: { qa_selector: 'email_address_field' }
.gl-mt-3 .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 %hr
%h4.gl-mt-0 %h4.gl-mt-0
= _('Linked emails (%{email_count})') % { email_count: @emails.load.size + 1 } = _('Linked emails (%{email_count})') % { email_count: @emails.load.size + 1 }
@ -56,8 +56,8 @@
%span.badge.badge-info= s_('Profiles|Notification email') %span.badge.badge-info= s_('Profiles|Notification email')
- unless email.confirmed? - unless email.confirmed?
- confirm_title = "#{email.confirmation_sent_at ? _('Resend confirmation email') : _('Send confirmation email')}" - 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') %span.sr-only= _('Remove')
= sprite_icon('remove') = sprite_icon('remove')

View File

@ -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-----'.") = 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 .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"

View File

@ -19,9 +19,9 @@
.float-right .float-right
%span.key-created-at %span.key-created-at
= s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(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') %span.sr-only= _('Remove')
= sprite_icon('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') %span.sr-only= _('Revoke')
= _('Revoke') = _('Revoke')

View File

@ -24,4 +24,4 @@
%button.btn.btn-success.js-add-ssh-key-validation-confirm-submit= _("Yes, add it") %button.btn.btn-success.js-add-ssh-key-validation-confirm-submit= _("Yes, add it")
.gl-mt-3 .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"

View File

@ -30,6 +30,6 @@
= f.label :password_confirmation, _('Password confirmation'), class: 'label-bold' = 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' } = f.password_field :password_confirmation, required: true, class: 'form-control', data: { qa_selector: 'confirm_password_field' }
.gl-mt-3.gl-mb-3 .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? - unless @user.password_automatically_set?
= link_to _('I forgot my password'), reset_profile_password_path, method: :put = link_to _('I forgot my password'), reset_profile_password_path, method: :put

View File

@ -28,4 +28,4 @@
.col-sm-10 .col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control' = f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions .form-actions
= f.submit _('Set new password'), class: 'btn btn-success' = f.submit _('Set new password'), class: 'gl-button btn btn-success'

View File

@ -143,4 +143,4 @@
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
.col-lg-8 .col-lg-8
.form-group .form-group
= f.submit _('Save changes'), class: 'btn btn-success' = f.submit _('Save changes'), class: 'gl-button btn btn-success'

View File

@ -36,7 +36,7 @@
.form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.") .form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.")
- if @user.avatar? - if @user.avatar?
%hr %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 %hr
.row .row
@ -46,7 +46,7 @@
.col-lg-8 .col-lg-8
= f.fields_for :status, @user.status do |status_form| = f.fields_for :status, @user.status do |status_form|
- emoji_button = button_tag type: :button, - 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 title: s_("Profiles|Add status emoji") do
- if @user.status - if @user.status
= emoji_icon @user.status.emoji = emoji_icon @user.status.emoji
@ -56,7 +56,7 @@
= sprite_icon('smile', css_class: 'award-control-icon-super-positive') = sprite_icon('smile', css_class: 'award-control-icon-super-positive')
- reset_message_button = button_tag type: :button, - reset_message_button = button_tag type: :button,
id: 'js-clear-user-status-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 title: s_("Profiles|Clear status") do
= sprite_icon("close") = sprite_icon("close")
@ -78,7 +78,7 @@
-# TODO: might need an entry in user/profile.md to describe some of these settings -# TODO: might need an entry in user/profile.md to describe some of these settings
-# https://gitlab.com/gitlab-org/gitlab-foss/issues/60070 -# https://gitlab.com/gitlab-org/gitlab-foss/issues/60070
%h5= ("Time zone") %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 } %input.hidden{ :type => 'hidden', :id => 'user_timezone', :name => 'user[timezone]', value: @user.timezone }
%hr %hr
@ -119,8 +119,8 @@
.help-block .help-block
= s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information") = 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 .gl-mt-3.gl-mb-3
= f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success' = f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-success'
= link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel' = 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.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } }
.modal-dialog .modal-dialog

View File

@ -9,5 +9,5 @@
%span.monospace{ data: { qa_selector: 'code_content' } }= code %span.monospace{ data: { qa_selector: 'code_content' } }= code
.d-flex .d-flex
= link_to _('Proceed'), profile_account_path, class: 'btn btn-success gl-mr-3', data: { qa_selector: 'proceed_button' } = 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: 'btn btn-default' = 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'

View File

@ -20,7 +20,7 @@
= link_to _('Disable two-factor authentication'), profile_two_factor_auth_path, = link_to _('Disable two-factor authentication'), profile_two_factor_auth_path,
method: :delete, 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.') }, 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| = form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f|
= submit_tag _('Regenerate recovery codes'), class: 'btn' = submit_tag _('Regenerate recovery codes'), class: 'btn'
@ -52,7 +52,7 @@
= label_tag :pin_code, _('Pin code'), class: "label-bold" = 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' } = text_field_tag :pin_code, nil, class: "form-control", required: true, data: { qa_selector: 'pin_code_field' }
.gl-mt-3 .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 %hr
@ -109,7 +109,7 @@
%span.gl-text-gray-500 %span.gl-text-gray-500
= _("no name set") = _("no name set")
%td= registration[:created_at].to_date.to_s(:medium) %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 - else
.settings-message.text-center .settings-message.text-center

View File

@ -22,4 +22,4 @@
- if partial_exists? "registrations/welcome/button" - if partial_exists? "registrations/welcome/button"
= render "registrations/welcome/button" = render "registrations/welcome/button"
- else - 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' }

View File

@ -0,0 +1,5 @@
---
title: Add Middleman Logo for Project Templates
merge_request: 44617
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Allow automatically selecting repository storage on move
merge_request: 45338
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Update Cycle Analytics with Value Stream Analytics in University
merge_request: 44244
author: Takuya Noguchi
type: other

View File

@ -0,0 +1,5 @@
---
title: Include PostgreSQL system identifier in usage ping
merge_request: 44972
author:
type: added

View File

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

View File

@ -194,7 +194,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `project_id` | integer | yes | ID of the project | | `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: Example request:

View File

@ -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 Dependencies should be kept to the minimum. The introduction of a new
dependency should be argued in the merge request, as per our [Approval dependency should be argued in the merge request, as per our [Approval
Guidelines](../code_review.md#approval-guidelines). Both [License 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 **(ULTIMATE)** and [Dependency
Scanning](../../user/application_security/dependency_scanning/index.md) Scanning](../../user/application_security/dependency_scanning/index.md)
**(ULTIMATE)** should be activated on all projects to ensure new dependencies **(ULTIMATE)** should be activated on all projects to ensure new dependencies

View File

@ -750,7 +750,8 @@ The following is example content of the Usage Ping payload.
}, },
"database": { "database": {
"adapter": "postgresql", "adapter": "postgresql",
"version": "9.6.15" "version": "9.6.15",
"pg_system_id": 6842684531675334351
}, },
"avg_cycle_analytics": { "avg_cycle_analytics": {
"issue": { "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 ## Exporting Usage Ping SQL queries and definitions
Two Rake tasks exist to export Usage Ping definitions. Two Rake tasks exist to export Usage Ping definitions.

View File

@ -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)** # Kerberos integration **(STARTER ONLY)**
GitLab can integrate with [Kerberos](https://web.mit.edu/kerberos/) as an authentication mechanism. 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 using Kerberos tokens, i.e., without having to send their password with each
operation. 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) ### HTTP Git access with Kerberos token (passwordless authentication)
#### Support for Git before 2.4 #### Support for Git before 2.4

View File

@ -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 - 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/) 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 Value Stream Analytics Overview (as of 2016)](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 - Product Page](https://about.gitlab.com/stages-devops-lifecycle/value-stream-analytics/)
### 3.9. Integrations ### 3.9. Integrations

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, how-to type: reference, how-to
--- ---
# Wiki # Wiki **(CORE)**
A separate system for documentation called Wiki, is built right into each 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 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 ## 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 Every wiki has a sidebar from which a short list of the created pages can be
found. The list is ordered alphabetically. 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 If you have many pages, not all will be listed in the sidebar. Click on
**View All Pages** to see all of them. **View All Pages** to see all of them.

View File

@ -69,7 +69,7 @@ module API
success Entities::ProjectRepositoryStorageMove success Entities::ProjectRepositoryStorageMove
end end
params do 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 end
post ':id/repository_storage_moves' do post ':id/repository_storage_moves' do
storage_move = user_project.repository_storage_moves.build( storage_move = user_project.repository_storage_moves.build(

View File

@ -45,14 +45,6 @@ module Gitlab
end end
def read_zip_file!(file_path) 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| job.artifacts_file.use_open_file do |file|
zip_file = Zip::File.new(file, false, true) zip_file = Zip::File.new(file, false, true)
entry = zip_file.find_entry(file_path) entry = zip_file.find_entry(file_path)
@ -69,25 +61,6 @@ module Gitlab
end end
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 def max_archive_size_in_mb
ActiveSupport::NumberHelper.number_to_human_size(MAX_ARCHIVE_SIZE) ActiveSupport::NumberHelper.number_to_human_size(MAX_ARCHIVE_SIZE)
end end

View File

@ -59,10 +59,6 @@ module Gitlab
::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false) ::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false)
end 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? def self.one_dimensional_matrix_enabled?
::Feature.enabled?(:one_dimensional_matrix, default_enabled: true) ::Feature.enabled?(:one_dimensional_matrix, default_enabled: true)
end end

View File

@ -33,6 +33,10 @@ module Gitlab
self.class.trace_bytes.increment({}, size.to_i) self.class.trace_bytes.increment({}, size.to_i)
end end
def observe_migration_duration(seconds)
self.class.finalize_histogram.observe({}, seconds.to_f)
end
def self.trace_operations def self.trace_operations
strong_memoize(:trace_operations) do strong_memoize(:trace_operations) do
name = :gitlab_ci_trace_operations_total name = :gitlab_ci_trace_operations_total
@ -50,6 +54,17 @@ module Gitlab
Gitlab::Metrics.counter(name, comment) Gitlab::Metrics.counter(name, comment)
end end
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 end
end end

View File

@ -250,6 +250,12 @@ module Gitlab
false false
end 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) def self.get_write_location(ar_connection)
row = ar_connection row = ar_connection
.select_all("SELECT pg_current_wal_insert_lsn()::text AS location") .select_all("SELECT pg_current_wal_insert_lsn()::text AS location")

View File

@ -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('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('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('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('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('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'), 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'),

View File

@ -3,14 +3,13 @@
module Gitlab module Gitlab
# Centralized class for repository size related calculations. # Centralized class for repository size related calculations.
class RepositorySizeChecker 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 # @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 @current_size_proc = current_size_proc
@limit = limit @limit = limit
@total_repository_size_excess = total_repository_size_excess.to_i @namespace = namespace
@additional_purchased_storage = additional_purchased_storage.to_i
@enabled = enabled && limit != 0 @enabled = enabled && limit != 0
end end
@ -44,6 +43,10 @@ module Gitlab
def error_message def error_message
@error_message_object ||= ::Gitlab::RepositorySizeErrorMessage.new(self) @error_message_object ||= ::Gitlab::RepositorySizeErrorMessage.new(self)
end end
private
attr_reader :namespace
end end
end end

View File

@ -4,7 +4,7 @@ module Gitlab
class RepositorySizeErrorMessage class RepositorySizeErrorMessage
include ActiveSupport::NumberHelper 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] # @param checher [RepositorySizeChecker]
def initialize(checker) def initialize(checker)

View File

@ -303,7 +303,8 @@ module Gitlab
}, },
database: { database: {
adapter: alt_usage_data { Gitlab::Database.adapter_name }, 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: { mail: {
smtp_server: alt_usage_data { ActionMailer::Base.smtp_settings[:address] } smtp_server: alt_usage_data { ActionMailer::Base.smtp_settings[:address] }

View File

@ -6,6 +6,10 @@ module RuboCop
module Cop module Cop
module Migration module Migration
# Cop that enforces always adding a limit on text columns # 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 class AddLimitToTextColumns < RuboCop::Cop::Cop
include MigrationHelpers include MigrationHelpers
@ -102,6 +106,8 @@ module RuboCop
# Check if there is an `add_text_limit` call for the provided # Check if there is an `add_text_limit` call for the provided
# table and attribute name # table and attribute name
def text_limit_missing?(node, table_name, attribute_name) def text_limit_missing?(node, table_name, attribute_name)
return false if encrypted_attribute_name?(attribute_name)
limit_found = false limit_found = false
node.each_descendant(:send) do |send_node| node.each_descendant(:send) do |send_node|
@ -118,6 +124,10 @@ module RuboCop
!limit_found !limit_found
end end
def encrypted_attribute_name?(attribute_name)
attribute_name.to_s.start_with?('encrypted_')
end
end end
end end
end end

View File

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

View File

@ -5,7 +5,6 @@ FactoryBot.define do
project project
source_storage_name { 'default' } source_storage_name { 'default' }
destination_storage_name { 'default' }
trait :scheduled do trait :scheduled do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:scheduled].value } state { ProjectRepositoryStorageMove.state_machines[:state].states[:scheduled].value }

View File

@ -13,7 +13,7 @@ import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types'; import * as types from '~/boards/stores/mutation_types';
import { inactiveId, ListType } from '~/boards/constants'; import { inactiveId, ListType } from '~/boards/constants';
import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql'; 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 => { const expectNotImplemented = action => {
it('is not implemented', () => { 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', () => { describe('showWelcomeList', () => {
it('should dispatch addList action', done => { it('should dispatch addList action', done => {
const state = { const state = {

View File

@ -18,17 +18,6 @@ RSpec.describe Gitlab::Ci::ArtifactFileReader do
expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom')
end 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 context 'when path does not exist' do
let(:path) { 'file/does/not/exist.txt' } let(:path) { 'file/does/not/exist.txt' }
let(:expected_error) do let(:expected_error) do

View File

@ -39,6 +39,12 @@ RSpec.describe Gitlab::Database do
end end
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 describe '.postgresql?' do
subject { described_class.postgresql? } subject { described_class.postgresql? }

View File

@ -3,16 +3,16 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::RepositorySizeChecker do RSpec.describe Gitlab::RepositorySizeChecker do
let_it_be(:namespace) { nil }
let(:current_size) { 0 } let(:current_size) { 0 }
let(:limit) { 50 } let(:limit) { 50 }
let(:enabled) { true } let(:enabled) { true }
subject do subject do
described_class.new( described_class.new(
current_size_proc: -> { current_size }, current_size_proc: -> { current_size.megabytes },
limit: limit, limit: limit.megabytes,
total_repository_size_excess: 0, namespace: namespace,
additional_purchased_storage: 0,
enabled: enabled enabled: enabled
) )
end end
@ -20,7 +20,7 @@ RSpec.describe Gitlab::RepositorySizeChecker do
describe '#enabled?' do describe '#enabled?' do
context 'when enabled' do context 'when enabled' do
it 'returns true' do it 'returns true' do
expect(subject.enabled?).to be_truthy expect(subject.enabled?).to eq(true)
end end
end end
@ -28,7 +28,7 @@ RSpec.describe Gitlab::RepositorySizeChecker do
let(:limit) { 0 } let(:limit) { 0 }
it 'returns false' do it 'returns false' do
expect(subject.enabled?).to be_falsey expect(subject.enabled?).to eq(false)
end end
end end
end end
@ -37,11 +37,11 @@ RSpec.describe Gitlab::RepositorySizeChecker do
let(:current_size) { 49 } let(:current_size) { 49 }
it 'returns true when changes go over' do 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 end
it 'returns false when changes do not go over' do 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
end end

View File

@ -3,11 +3,11 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::RepositorySizeErrorMessage do RSpec.describe Gitlab::RepositorySizeErrorMessage do
let_it_be(:namespace) { build(:namespace) }
let(:checker) do let(:checker) do
Gitlab::RepositorySizeChecker.new( Gitlab::RepositorySizeChecker.new(
current_size_proc: -> { 15.megabytes }, current_size_proc: -> { 15.megabytes },
total_repository_size_excess: 0, namespace: namespace,
additional_purchased_storage: 0,
limit: 10.megabytes limit: 10.megabytes
) )
end end
@ -15,6 +15,10 @@ RSpec.describe Gitlab::RepositorySizeErrorMessage do
let(:message) { checker.error_message } let(:message) { checker.error_message }
let(:base_message) { 'because this repository has exceeded its size limit of 10 MB by 5 MB' } 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 'error messages' do
describe '#commit_error' do describe '#commit_error' do
it 'returns the correct message' do it 'returns the correct message' do

View File

@ -723,6 +723,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(subject[:git][:version]).to eq(Gitlab::Git.version) expect(subject[:git][:version]).to eq(Gitlab::Git.version)
expect(subject[:database][:adapter]).to eq(Gitlab::Database.adapter_name) expect(subject[:database][:adapter]).to eq(Gitlab::Database.adapter_name)
expect(subject[:database][:version]).to eq(Gitlab::Database.version) 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[:mail][:smtp_server]).to eq(ActionMailer::Base.smtp_settings[:address])
expect(subject[:gitaly][:version]).to be_present expect(subject[:gitaly][:version]).to be_present
expect(subject[:gitaly][:servers]).to be >= 1 expect(subject[:gitaly][:servers]).to be >= 1

View File

@ -43,6 +43,18 @@ RSpec.describe ProjectRepositoryStorageMove, type: :model do
end end
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 describe 'state transitions' do
let(:project) { create(:project) } let(:project) { create(:project) }

View File

@ -666,8 +666,7 @@ RSpec.describe Snippet do
let(:checker) { subject.repository_size_checker } let(:checker) { subject.repository_size_checker }
let(:current_size) { 60 } let(:current_size) { 60 }
let(:total_repository_size_excess) { 0 } let(:namespace) { nil }
let(:additional_purchased_storage) { 0 }
before do before do
allow(subject.repository).to receive(:size).and_return(current_size) allow(subject.repository).to receive(:size).and_return(current_size)

View File

@ -145,10 +145,17 @@ RSpec.describe API::ProjectRepositoryStorageMoves do
context 'destination_storage_name is missing' do context 'destination_storage_name is missing' do
let(:destination_storage_name) { nil } let(:destination_storage_name) { nil }
it 'returns a validation error' do it 'schedules a project repository storage move' do
create_project_repository_storage_move 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 end
end end

View File

@ -129,6 +129,28 @@ RSpec.describe RuboCop::Cop::Migration::AddLimitToTextColumns, type: :rubocop do
end end
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 context 'on down' do
it 'registers no offense' do it 'registers no offense' do
expect_no_offenses(<<~RUBY) expect_no_offenses(<<~RUBY)

View File

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

View File

@ -131,6 +131,18 @@ RSpec.describe Ci::UpdateBuildStateService do
.with(operation: :finalized) .with(operation: :finalized)
end 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 context 'when trace checksum is not valid' do
it 'increments invalid trace metric' do it 'increments invalid trace metric' do
execute_with_stubbed_metrics! execute_with_stubbed_metrics!

View File

@ -29,7 +29,7 @@ RSpec.shared_examples 'checker size exceeded' do
let(:current_size) { 51 } let(:current_size) { 51 }
it 'returns zero' do it 'returns zero' do
expect(subject.exceeded_size).to eq(1) expect(subject.exceeded_size).to eq(1.megabytes)
end end
end end
@ -37,7 +37,7 @@ RSpec.shared_examples 'checker size exceeded' do
let(:current_size) { 50 } let(:current_size) { 50 }
it 'returns zero' do it 'returns zero' do
expect(subject.exceeded_size(1)).to eq(1) expect(subject.exceeded_size(1.megabytes)).to eq(1.megabytes)
end end
end end
@ -45,7 +45,7 @@ RSpec.shared_examples 'checker size exceeded' do
let(:current_size) { 49 } let(:current_size) { 49 }
it 'returns zero' do it 'returns zero' do
expect(subject.exceeded_size(1)).to eq(0) expect(subject.exceeded_size(1.megabytes)).to eq(0)
end end
end end
end end

View File

@ -4,8 +4,7 @@ RSpec.shared_examples 'size checker for snippet' do |action|
it 'sets up size checker', :aggregate_failures do it 'sets up size checker', :aggregate_failures do
expect(checker.current_size).to eq(current_size.megabytes) expect(checker.current_size).to eq(current_size.megabytes)
expect(checker.limit).to eq(Gitlab::CurrentSettings.snippet_size_limit) 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.enabled?).to eq(true)
expect(checker.instance_variable_get(:@namespace)).to eq(namespace)
end end
end end