diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-01 12:08:56 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-01 12:08:56 +0000 |
commit | 50d66f5ece57dcfbe074d97703691a8d3c38f4ac (patch) | |
tree | c96aa5ffd1cb73c18e53356680cb9792d24c257b | |
parent | cfec4ed6fe77e4150b1ea83b87f407aa0cca944c (diff) | |
download | gitlab-ce-50d66f5ece57dcfbe074d97703691a8d3c38f4ac.tar.gz |
Add latest changes from gitlab-org/gitlab@master
100 files changed, 831 insertions, 360 deletions
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 5a1e5dc316a..2ca2a1d28ce 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -278,10 +278,6 @@ Rails/SaveBang: - 'spec/lib/gitlab/import_export/uploads_manager_spec.rb' - 'spec/lib/gitlab/import_export/uploads_saver_spec.rb' - 'spec/lib/gitlab/import_export/wiki_restorer_spec.rb' - - 'spec/lib/gitlab/legacy_github_import/importer_spec.rb' - - 'spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb' - - 'spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb' - - 'spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb' - 'spec/lib/gitlab/lets_encrypt/client_spec.rb' - 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb' - 'spec/lib/gitlab/markdown_cache/redis/store_spec.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index a8a3d86d5a3..0020837c7c6 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -c5786b09543e40acc6e05bd4d29f6d89106b8e8a +46db2b9e1da386cc081455eef16f5fa1a9fefa51 diff --git a/app/assets/javascripts/boards/components/board_card_loading_skeleton.vue b/app/assets/javascripts/boards/components/board_card_loading_skeleton.vue new file mode 100644 index 00000000000..15bff1226a6 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_card_loading_skeleton.vue @@ -0,0 +1,26 @@ +<script> +import { GlSkeletonLoader } from '@gitlab/ui'; + +export default { + name: 'BoardCardLoading', + components: { + GlSkeletonLoader, + }, +}; +</script> + +<template> + <div + class="board-card-skeleton gl-mb-3 gl-bg-white gl-rounded-base gl-p-5 gl-border-1 gl-border-solid gl-border-gray-50" + > + <div class="board-card-skeleton-inner"> + <gl-skeleton-loader :width="340" :height="100"> + <rect width="340" height="16" rx="4" /> + <rect y="30" width="118" height="16" rx="8" /> + <rect x="122" y="30" width="130" height="16" rx="8" /> + <rect y="62" width="38" height="16" rx="4" /> + <circle cx="320" cy="68" r="16" /> + </gl-skeleton-loader> + </div> + </div> +</template> diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index b30d766f15a..8423457ac07 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -127,7 +127,7 @@ export default { </component> <epics-swimlanes - v-else + v-else-if="boardListsToUse.length" ref="swimlanes" :lists="boardListsToUse" :can-admin-list="canAdminList" diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 944eb25bf7e..144cae15ab3 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -10,7 +10,7 @@ import ProjectSelect from './project_select.vue'; export default { name: 'BoardNewIssue', i18n: { - submit: __('Submit issue'), + submit: __('Create issue'), cancel: __('Cancel'), }, components: { diff --git a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue index 16f23dfff0e..1218941065f 100644 --- a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue +++ b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue @@ -121,7 +121,7 @@ export default { variant="success" category="primary" type="submit" - >{{ __('Submit issue') }}</gl-button + >{{ __('Create issue') }}</gl-button > <gl-button ref="cancelButton" diff --git a/app/assets/javascripts/boards/graphql/issue.fragment.graphql b/app/assets/javascripts/boards/graphql/issue.fragment.graphql index 1395bef39ed..7ecf9261214 100644 --- a/app/assets/javascripts/boards/graphql/issue.fragment.graphql +++ b/app/assets/javascripts/boards/graphql/issue.fragment.graphql @@ -7,6 +7,10 @@ fragment IssueNode on Issue { referencePath: reference(full: true) dueDate timeEstimate + totalTimeSpent + humanTimeEstimate + humanTotalTimeSpent + emailsDisabled confidential webUrl subscribed diff --git a/app/assets/javascripts/members/components/avatars/user_avatar.vue b/app/assets/javascripts/members/components/avatars/user_avatar.vue index 658fb43cecb..9687eacb036 100644 --- a/app/assets/javascripts/members/components/avatars/user_avatar.vue +++ b/app/assets/javascripts/members/components/avatars/user_avatar.vue @@ -5,7 +5,6 @@ import { GlBadge, GlSafeHtmlDirective as SafeHtml, } from '@gitlab/ui'; -import { mapState } from 'vuex'; import { generateBadges } from 'ee_else_ce/members/utils'; import { glEmojiTag } from '~/emoji'; import { __ } from '~/locale'; @@ -24,6 +23,7 @@ export default { directives: { SafeHtml, }, + inject: ['canManageMembers'], props: { member: { type: Object, @@ -35,7 +35,6 @@ export default { }, }, computed: { - ...mapState(['canManageMembers']), user() { return this.member.user; }, diff --git a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue index 039ee9a0207..9e58c7022b8 100644 --- a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue +++ b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue @@ -37,13 +37,14 @@ export default { ], }, ], + inject: ['sourceId', 'canManageMembers'], data() { return { initialFilterValue: [], }; }, computed: { - ...mapState(['sourceId', 'filteredSearchBar', 'canManageMembers']), + ...mapState(['filteredSearchBar']), tokens() { return this.$options.availableTokens.filter((token) => { if ( diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue index 9a3edff19ff..5db29951d94 100644 --- a/app/assets/javascripts/members/components/table/members_table.vue +++ b/app/assets/javascripts/members/components/table/members_table.vue @@ -31,8 +31,9 @@ export default { LdapOverrideConfirmationModal: () => import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'), }, + inject: ['currentUserId'], computed: { - ...mapState(['members', 'tableFields', 'tableAttrs', 'currentUserId']), + ...mapState(['members', 'tableFields', 'tableAttrs']), filteredFields() { return FIELDS.filter( (field) => this.tableFields.includes(field.key) && this.showField(field), diff --git a/app/assets/javascripts/members/components/table/members_table_cell.vue b/app/assets/javascripts/members/components/table/members_table_cell.vue index 1f537740f94..3436bcab2fc 100644 --- a/app/assets/javascripts/members/components/table/members_table_cell.vue +++ b/app/assets/javascripts/members/components/table/members_table_cell.vue @@ -1,5 +1,4 @@ <script> -import { mapState } from 'vuex'; import { MEMBER_TYPES } from '../../constants'; import { isGroup, @@ -12,6 +11,7 @@ import { export default { name: 'MembersTableCell', + inject: ['currentUserId'], props: { member: { type: Object, @@ -19,7 +19,6 @@ export default { }, }, computed: { - ...mapState(['currentUserId']), isGroup() { return isGroup(this.member); }, diff --git a/app/assets/javascripts/members/index.js b/app/assets/javascripts/members/index.js index fe174d9beb6..2f3589bbf6a 100644 --- a/app/assets/javascripts/members/index.js +++ b/app/assets/javascripts/members/index.js @@ -22,10 +22,11 @@ export const initMembersApp = ( Vue.use(Vuex); Vue.use(GlToast); + const { sourceId, canManageMembers, ...vuexStoreAttributes } = parseDataAttributes(el); + const store = new Vuex.Store( membersStore({ - ...parseDataAttributes(el), - currentUserId: gon.current_user_id || null, + ...vuexStoreAttributes, tableFields, tableAttrs, tableSortableFields, @@ -38,6 +39,11 @@ export const initMembersApp = ( el, components: { App }, store, + provide: { + currentUserId: gon.current_user_id || null, + sourceId, + canManageMembers, + }, render: (createElement) => createElement('app'), }); }; diff --git a/app/assets/javascripts/members/store/state.js b/app/assets/javascripts/members/store/state.js index 23a7983adcc..4006b4b501d 100644 --- a/app/assets/javascripts/members/store/state.js +++ b/app/assets/javascripts/members/store/state.js @@ -1,8 +1,5 @@ export default ({ members, - sourceId, - currentUserId, - canManageMembers, tableFields, tableAttrs, tableSortableFields, @@ -11,9 +8,6 @@ export default ({ filteredSearchBar, }) => ({ members, - sourceId, - currentUserId, - canManageMembers, tableFields, tableAttrs, tableSortableFields, diff --git a/app/assets/javascripts/pages/projects/packages/infrastructure_registry/index/index.js b/app/assets/javascripts/pages/projects/packages/infrastructure_registry/index/index.js new file mode 100644 index 00000000000..c94782fdf1b --- /dev/null +++ b/app/assets/javascripts/pages/projects/packages/infrastructure_registry/index/index.js @@ -0,0 +1,3 @@ +import initPackageList from '~/packages/list/packages_list_app_bundle'; + +initPackageList(); diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 54a2284a3ab..8dd0ffabb82 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -421,7 +421,6 @@ img.emoji { .mw-460 { max-width: 460px; } .mw-6em { max-width: 6em; } .mw-70p { max-width: 70%; } -.mw-90p { max-width: 90%; } // By default flex items don't shrink below their minimum content size. // To change this, these clases set a min-width or min-height diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss index 4ec9e414749..568d71105fa 100644 --- a/app/assets/stylesheets/page_bundles/boards.scss +++ b/app/assets/stylesheets/page_bundles/boards.scss @@ -471,3 +471,13 @@ .board-header-collapsed-info-icon:hover { color: var(--gray-900, $gray-900); } + +.board-card-skeleton { + height: 110px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + + .board-card-skeleton-inner { + width: 340px; + height: 100px; + } +} diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 7111d3d4107..a114a1dc82d 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -1,4 +1,5 @@ -%commit-description-base { +.commit-description, +.commit-row-description { padding: $gl-padding-8 0 $gl-padding-8 $gl-padding-8; margin-top: $gl-padding-8; border: 0; @@ -10,10 +11,6 @@ color: $gl-text-color-secondary; } -.commit-description { - @extend %commit-description-base; -} - .commit-box { border-top: 1px solid $border-color; padding: $gl-padding 0; @@ -249,7 +246,6 @@ } .commit-row-description { - @extend %commit-description-base; display: none; flex: 1; } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index ef737e11799..14cff5b038a 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -36,7 +36,7 @@ } .file-title { - @extend .monospace; + @include gl-font-monospace; line-height: 35px; padding-top: 7px; padding-bottom: 7px; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index d8d6b4ce416..2ec2da9241b 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -160,17 +160,6 @@ vertical-align: top; } - - .notification-dropdown { - .dropdown-menu { - @extend .dropdown-menu-right; - } - - .icon { - fill: $gl-text-color-secondary; - } - } - .new-project-subgroup { .dropdown-primary { min-width: 115px; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 540060d60de..c05216ac6e6 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -26,13 +26,6 @@ text-align: right; white-space: nowrap; } - - .key { - @extend .badge.badge-pill; - background-color: $label-inverse-bg; - font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace; - padding: 3px 5px; - } } .documentation { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index ee3ecf46f60..0a36051948e 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -633,7 +633,7 @@ } .btn-link:hover { - @extend a:hover; + color: $blue-800; text-decoration: none; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index bcb950a2b87..16f96ebadc9 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -136,10 +136,6 @@ } } - .notification-dropdown .dropdown-menu { - @extend .dropdown-menu-right; - } - .download-button { @include media-breakpoint-down(md) { margin-left: 0; @@ -838,7 +834,7 @@ pre.light-well { } .form-control { - @extend .monospace; + @include gl-font-monospace; background-color: $white; border-color: $border-color; font-size: 14px; diff --git a/app/controllers/projects/packages/infrastructure_registry_controller.rb b/app/controllers/projects/packages/infrastructure_registry_controller.rb new file mode 100644 index 00000000000..22ae1d65013 --- /dev/null +++ b/app/controllers/projects/packages/infrastructure_registry_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Projects + module Packages + class InfrastructureRegistryController < Projects::ApplicationController + feature_category :infrastructure_as_code + end + end +end diff --git a/app/controllers/registrations/experience_levels_controller.rb b/app/controllers/registrations/experience_levels_controller.rb index b6ed0366177..3a721823d89 100644 --- a/app/controllers/registrations/experience_levels_controller.rb +++ b/app/controllers/registrations/experience_levels_controller.rb @@ -6,7 +6,7 @@ module Registrations before_action :ensure_namespace_path_param - feature_category :navigation + feature_category :onboarding def update current_user.experience_level = params[:experience_level] diff --git a/app/finders/git_refs_finder.rb b/app/finders/git_refs_finder.rb index 2289b34e562..11af659d37c 100644 --- a/app/finders/git_refs_finder.rb +++ b/app/finders/git_refs_finder.rb @@ -33,15 +33,21 @@ class GitRefsFinder end def filter_refs_with_prefix(refs, prefix) - refs.select { |ref| ref.name.upcase.starts_with?(prefix.upcase) } + prefix = prefix.downcase + + refs.select { |ref| ref.name.downcase.starts_with?(prefix) } end def filter_refs_with_suffix(refs, suffix) - refs.select { |ref| ref.name.upcase.ends_with?(suffix.upcase) } + suffix = suffix.downcase + + refs.select { |ref| ref.name.downcase.ends_with?(suffix) } end def filter_refs_by_name(refs, term) - refs.select { |ref| ref.name.upcase.include?(term.upcase) } + term = term.downcase + + refs.select { |ref| ref.name.downcase.include?(term) } end def set_exact_match_as_first_result(matches, term) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index b6b9ad806f7..70c094c7ec6 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -410,6 +410,10 @@ module ProjectsHelper nav_tabs << :container_registry end + if Feature.enabled?(:infrastructure_registry_page) + nav_tabs << :infrastructure_registry + end + # Pipelines feature is tied to presence of builds if can?(current_user, :read_build, project) nav_tabs << :pipelines diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb index 0ad1ed2fce8..3b61840805a 100644 --- a/app/models/ci/processable.rb +++ b/app/models/ci/processable.rb @@ -165,7 +165,13 @@ module Ci end def all_dependencies - dependencies.all + if Feature.enabled?(:preload_associations_jobs_request_api_endpoint, project, default_enabled: :yaml) + strong_memoize(:all_dependencies) do + dependencies.all + end + else + dependencies.all + end end private diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb index 297dc503294..6978bc46475 100644 --- a/app/presenters/ci/build_runner_presenter.rb +++ b/app/presenters/ci/build_runner_presenter.rb @@ -55,6 +55,18 @@ module Ci specs end + # rubocop: disable CodeReuse/ActiveRecord + def all_dependencies + dependencies = super + + if Feature.enabled?(:preload_associations_jobs_request_api_endpoint, project, default_enabled: :yaml) + ActiveRecord::Associations::Preloader.new.preload(dependencies, :job_artifacts_archive) + end + + dependencies + end + # rubocop: enable CodeReuse/ActiveRecord + private def create_archive(artifacts) diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb index 4fb61c9273f..845d7a0c52a 100644 --- a/app/serializers/merge_request_poll_cached_widget_entity.rb +++ b/app/serializers/merge_request_poll_cached_widget_entity.rb @@ -50,6 +50,14 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity MergeRequests::PipelineEntity.represent(merge_request.actual_head_pipeline, options) end + expose :merge_pipeline, if: ->(mr, _) { + Feature.enabled?(:merge_request_cached_merge_pipeline_serializer, mr.project, default_enabled: :yaml) && + mr.merged? && + can?(request.current_user, :read_pipeline, mr.target_project) + } do |merge_request, options| + MergeRequests::PipelineEntity.represent(merge_request.merge_pipeline, options) + end + # Paths # expose :target_branch_commits_path do |merge_request| diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb index e8d7cabc68e..fc064a3a3b1 100644 --- a/app/serializers/merge_request_poll_widget_entity.rb +++ b/app/serializers/merge_request_poll_widget_entity.rb @@ -19,7 +19,11 @@ class MergeRequestPollWidgetEntity < Grape::Entity # User entities expose :merge_user, using: UserEntity - expose :merge_pipeline, if: ->(mr, _) { mr.merged? && can?(request.current_user, :read_pipeline, mr.target_project)} do |merge_request, options| + expose :merge_pipeline, if: ->(mr, _) { + Feature.disabled?(:merge_request_cached_merge_pipeline_serializer, mr.project, default_enabled: :yaml) && + mr.merged? && + can?(request.current_user, :read_pipeline, mr.target_project) + } do |merge_request, options| MergeRequests::PipelineEntity.represent(merge_request.merge_pipeline, options) end diff --git a/app/views/admin/application_settings/_realtime.html.haml b/app/views/admin/application_settings/_realtime.html.haml index bee120d2f78..545c27d2a7e 100644 --- a/app/views/admin/application_settings/_realtime.html.haml +++ b/app/views/admin/application_settings/_realtime.html.haml @@ -3,15 +3,10 @@ %fieldset .form-group - = f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'label-bold' + = f.label :polling_interval_multiplier, _('Polling interval multiplier'), class: 'label-bold' = f.text_field :polling_interval_multiplier, class: 'form-control gl-form-input' .form-text.text-muted - Change this value to influence how frequently the GitLab UI polls for updates. - If you set the value to 2 all polling intervals are multiplied - by 2, which means that polling happens half as frequently. - The multiplier can also have a decimal value. - The default value (1) is a reasonable choice for the majority of GitLab - installations. Set to 0 to completely disable polling. + = _("Change this value to influence how frequently the GitLab UI polls for updates. If you set the value to 2 all polling intervals are multiplied by 2, which means that polling happens half as frequently. The multiplier can also have a decimal value. The default value (1) is a reasonable choice for the majority of GitLab installations. Set to 0 to completely disable polling.") = link_to sprite_icon('question-o'), help_page_path('administration/polling') - = f.submit 'Save changes', class: "gl-button btn btn-confirm" + = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_repository_check.html.haml b/app/views/admin/application_settings/_repository_check.html.haml index ee0281b6e33..edf6853a1aa 100644 --- a/app/views/admin/application_settings/_repository_check.html.haml +++ b/app/views/admin/application_settings/_repository_check.html.haml @@ -3,56 +3,51 @@ %fieldset .sub-section - %h4 Repository checks + %h4= _("Repository checks") .form-group .form-check = f.check_box :repository_checks_enabled, class: 'form-check-input' = f.label :repository_checks_enabled, class: 'form-check-label' do - Enable Repository Checks + = _("Enable Repository Checks") .form-text.text-muted - GitLab will periodically run - %a{ href: 'https://git-scm.com/docs/git-fsck', target: 'blank' } 'git fsck' - in all project and wiki repositories to look for silent disk corruption issues. + - link_to_git_fsck = link_to('git fsck', 'https://git-scm.com/docs/git-fsck', target: '_blank') + = _("GitLab will periodically run %{link_to_git_fsck} in all project and wiki repositories to look for silent disk corruption issues.").html_safe % { link_to_git_fsck: link_to_git_fsck } .form-group .form-text.text-muted - If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database. + = _("If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.") - clear_repository_checks_link = _('Clear all repository checks') - clear_repository_checks_message = _('This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?') = link_to clear_repository_checks_link, clear_repository_check_states_admin_application_settings_path, data: { confirm: clear_repository_checks_message }, method: :put, class: "gl-button btn btn-sm btn-danger" .sub-section - %h4 Housekeeping + %h4= _("Housekeeping") .form-group .form-check = f.check_box :housekeeping_enabled, class: 'form-check-input' = f.label :housekeeping_enabled, class: 'form-check-label' do - Enable automatic repository housekeeping (git repack, git gc) + = _("Enable automatic repository housekeeping (git repack, git gc)") .form-text.text-muted - If you keep automatic housekeeping disabled for a long time Git - repository access on your GitLab server will become slower and your - repositories will use more disk space. We recommend to always leave - this enabled. + = _("If you keep automatic housekeeping disabled for a long time Git repository access on your GitLab server will become slower and your repositories will use more disk space. We recommend to always leave this enabled.") .form-check = f.check_box :housekeeping_bitmaps_enabled, class: 'form-check-input' = f.label :housekeeping_bitmaps_enabled, class: 'form-check-label' do - Enable Git pack file bitmap creation + = _("Enable Git pack file bitmap creation") .form-text.text-muted - Creating pack file bitmaps makes housekeeping take a little longer but - bitmaps should accelerate 'git clone' performance. + = _("Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance.") .form-group = f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'label-bold' = f.number_field :housekeeping_incremental_repack_period, class: 'form-control gl-form-input' .form-text.text-muted - Number of Git pushes after which an incremental 'git repack' is run. + = _("Number of Git pushes after which an incremental 'git repack' is run.") .form-group = f.label :housekeeping_full_repack_period, 'Full repack period', class: 'label-bold' = f.number_field :housekeeping_full_repack_period, class: 'form-control gl-form-input' .form-text.text-muted - Number of Git pushes after which a full 'git repack' is run. + = _("Number of Git pushes after which a full 'git repack' is run.") .form-group - = f.label :housekeeping_gc_period, 'Git GC period', class: 'label-bold' + = f.label :housekeeping_gc_period, _('Git GC period'), class: 'label-bold' = f.number_field :housekeeping_gc_period, class: 'form-control gl-form-input' .form-text.text-muted - Number of Git pushes after which 'git gc' is run. + = _("Number of Git pushes after which 'git gc' is run.") = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/app/views/layouts/nav/sidebar/_project_packages_link.html.haml b/app/views/layouts/nav/sidebar/_project_packages_link.html.haml index e9989abe5a0..b28468a7969 100644 --- a/app/views/layouts/nav/sidebar/_project_packages_link.html.haml +++ b/app/views/layouts/nav/sidebar/_project_packages_link.html.haml @@ -1,14 +1,14 @@ - packages_link = project_nav_tab?(:packages) ? project_packages_path(@project) : project_container_registry_index_path(@project) - if (project_nav_tab?(:packages) || project_nav_tab?(:container_registry)) - = nav_link controller: [:packages, :repositories] do + = nav_link controller: [:packages, :repositories, :infrastructure_registry] do = link_to packages_link, data: { qa_selector: 'packages_link' } do .nav-icon-container = sprite_icon('package') %span.nav-item-name = _('Packages & Registries') %ul.sidebar-sub-level-items - = nav_link(controller: [:packages, :repositories], html_options: { class: "fly-out-top-item" } ) do + = nav_link(controller: [:packages, :repositories, :infrastructure_registry], html_options: { class: "fly-out-top-item" } ) do = link_to packages_link do %strong.fly-out-top-item-name = _('Packages & Registries') @@ -21,3 +21,7 @@ = nav_link controller: :repositories do = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry', title: _('Container Registry') do %span= _('Container Registry') + - if project_nav_tab? :infrastructure_registry + = nav_link controller: :infrastructure_registry do + = link_to project_infrastructure_registry_index_path(@project), title: _('Infrastructure Registry') do + %span= _('Infrastructure Registry') diff --git a/app/views/projects/packages/infrastructure_registry/index.html.haml b/app/views/projects/packages/infrastructure_registry/index.html.haml new file mode 100644 index 00000000000..e8be9051275 --- /dev/null +++ b/app/views/projects/packages/infrastructure_registry/index.html.haml @@ -0,0 +1,10 @@ +- page_title _("Infrastructure Registry") +- @content_class = "limit-container-width" unless fluid_layout + +.row + .col-12 + #js-vue-packages-list{ data: { resource_id: @project.id, + page_type: 'project', + empty_list_help_url: help_page_path('user/packages/package_registry/index'), + empty_list_illustration: image_path('illustrations/no-packages.svg'), + package_help_url: help_page_path('user/packages/index') } } diff --git a/changelogs/unreleased/325630-cablett-filter-epics-by-reaction-be.yml b/changelogs/unreleased/325630-cablett-filter-epics-by-reaction-be.yml new file mode 100644 index 00000000000..301a0d67b10 --- /dev/null +++ b/changelogs/unreleased/325630-cablett-filter-epics-by-reaction-be.yml @@ -0,0 +1,5 @@ +--- +title: Allow user to filter epics by their reaction emoji via GraphQL +merge_request: 58211 +author: +type: added diff --git a/changelogs/unreleased/9152-check-sso-status-on-git-activity-and-direct-user-to-sso.yml b/changelogs/unreleased/9152-check-sso-status-on-git-activity-and-direct-user-to-sso.yml new file mode 100644 index 00000000000..2b681363bfe --- /dev/null +++ b/changelogs/unreleased/9152-check-sso-status-on-git-activity-and-direct-user-to-sso.yml @@ -0,0 +1,5 @@ +--- +title: Group SAML - Check SSO status on Git activity +merge_request: 56867 +author: +type: added diff --git a/changelogs/unreleased/Externalise-strings-in-_realtime-html-haml.yml b/changelogs/unreleased/Externalise-strings-in-_realtime-html-haml.yml new file mode 100644 index 00000000000..9cf3ee7d762 --- /dev/null +++ b/changelogs/unreleased/Externalise-strings-in-_realtime-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Externalise strings in /application_settings/_realtime.html.haml +merge_request: 58039 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/Externalise-strings-in-_repository_check-html-haml.yml b/changelogs/unreleased/Externalise-strings-in-_repository_check-html-haml.yml new file mode 100644 index 00000000000..9d481093680 --- /dev/null +++ b/changelogs/unreleased/Externalise-strings-in-_repository_check-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Externalise strings in /application_settings/_repository_check.html.haml +merge_request: 58058 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/issue-220040-fix-rails-savebang-legacy-github-import-module.yml b/changelogs/unreleased/issue-220040-fix-rails-savebang-legacy-github-import-module.yml new file mode 100644 index 00000000000..c9dba61ef6c --- /dev/null +++ b/changelogs/unreleased/issue-220040-fix-rails-savebang-legacy-github-import-module.yml @@ -0,0 +1,5 @@ +--- +title: Fix Rails/SaveBang Rubocop offenses for legacy github import +merge_request: 58054 +author: Huzaifa Iftikhar @huzaifaiftikhar +type: fixed diff --git a/changelogs/unreleased/issue_322686-improve_award_emoji_filtering_query.yml b/changelogs/unreleased/issue_322686-improve_award_emoji_filtering_query.yml new file mode 100644 index 00000000000..a7115072bf3 --- /dev/null +++ b/changelogs/unreleased/issue_322686-improve_award_emoji_filtering_query.yml @@ -0,0 +1,5 @@ +--- +title: Add composite index to support epic filtering by award emoji +merge_request: 57759 +author: +type: performance diff --git a/changelogs/unreleased/ref-finder-performance-improvement.yml b/changelogs/unreleased/ref-finder-performance-improvement.yml new file mode 100644 index 00000000000..f1e1888e58b --- /dev/null +++ b/changelogs/unreleased/ref-finder-performance-improvement.yml @@ -0,0 +1,5 @@ +--- +title: Minor performance improvement for ref finder +merge_request: 58099 +author: +type: performance diff --git a/changelogs/unreleased/rename-create-issue.yml b/changelogs/unreleased/rename-create-issue.yml new file mode 100644 index 00000000000..468ee2de8ae --- /dev/null +++ b/changelogs/unreleased/rename-create-issue.yml @@ -0,0 +1,5 @@ +--- +title: Rename Submit issue to Create issue in boards and docs +merge_request: 58243 +author: Yogi (@yo) +type: changed diff --git a/config/feature_flags/development/infrastructure_registry_page.yml b/config/feature_flags/development/infrastructure_registry_page.yml new file mode 100644 index 00000000000..fcb34f9d05b --- /dev/null +++ b/config/feature_flags/development/infrastructure_registry_page.yml @@ -0,0 +1,8 @@ +--- +name: infrastructure_registry_page +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57338 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326460 +milestone: '13.11' +type: development +group: group::package +default_enabled: false diff --git a/config/feature_flags/development/merge_request_cached_merge_pipeline_serializer.yml b/config/feature_flags/development/merge_request_cached_merge_pipeline_serializer.yml new file mode 100644 index 00000000000..506e59f001b --- /dev/null +++ b/config/feature_flags/development/merge_request_cached_merge_pipeline_serializer.yml @@ -0,0 +1,8 @@ +--- +name: merge_request_cached_merge_pipeline_serializer +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57827 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326317 +milestone: '13.11' +type: development +group: group::source code +default_enabled: false diff --git a/config/feature_flags/development/preload_associations_jobs_request_api_endpoint.yml b/config/feature_flags/development/preload_associations_jobs_request_api_endpoint.yml new file mode 100644 index 00000000000..92f63808bd8 --- /dev/null +++ b/config/feature_flags/development/preload_associations_jobs_request_api_endpoint.yml @@ -0,0 +1,8 @@ +--- +name: preload_associations_jobs_request_api_endpoint +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57694 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326477 +milestone: "13.11" +type: development +group: group::continuous integration +default_enabled: true diff --git a/config/routes/project.rb b/config/routes/project.rb index 363d23590bf..09b212bc1a2 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -50,6 +50,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end + resources :infrastructure_registry, only: [:index], module: :packages + resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do collection do resources :artifacts, only: [] do diff --git a/db/migrate/20210312174321_add_enforced_git_check_to_saml_provider.rb b/db/migrate/20210312174321_add_enforced_git_check_to_saml_provider.rb new file mode 100644 index 00000000000..89553a53084 --- /dev/null +++ b/db/migrate/20210312174321_add_enforced_git_check_to_saml_provider.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddEnforcedGitCheckToSamlProvider < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def up + add_column :saml_providers, :git_check_enforced, :boolean, default: false, null: false + end + + def down + remove_column :saml_providers, :git_check_enforced + end +end diff --git a/db/migrate/20210329192716_add_composite_index_to_award_emoji.rb b/db/migrate/20210329192716_add_composite_index_to_award_emoji.rb new file mode 100644 index 00000000000..ce37afdbc29 --- /dev/null +++ b/db/migrate/20210329192716_add_composite_index_to_award_emoji.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddCompositeIndexToAwardEmoji < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'idx_award_emoji_on_user_emoji_name_awardable_type_awardable_id' + + disable_ddl_transaction! + + def up + add_concurrent_index :award_emoji, %i[user_id name awardable_type awardable_id], name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :award_emoji, INDEX_NAME + end +end diff --git a/db/migrate/20210331180118_remove_deprecated_index_from_award_emoji.rb b/db/migrate/20210331180118_remove_deprecated_index_from_award_emoji.rb new file mode 100644 index 00000000000..b8787eb171c --- /dev/null +++ b/db/migrate/20210331180118_remove_deprecated_index_from_award_emoji.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class RemoveDeprecatedIndexFromAwardEmoji < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'index_award_emoji_on_user_id_and_name' + + disable_ddl_transaction! + + def up + # Index deprecated in favor of idx_award_emoji_on_user_emoji_name_awardable_type_awardable_id + remove_concurrent_index_by_name(:award_emoji, INDEX_NAME) + end + + def down + add_concurrent_index(:award_emoji, [:user_id, :name], name: INDEX_NAME) + end +end diff --git a/db/schema_migrations/20210312174321 b/db/schema_migrations/20210312174321 new file mode 100644 index 00000000000..5126ab2675d --- /dev/null +++ b/db/schema_migrations/20210312174321 @@ -0,0 +1 @@ +4fa88193ae328f04465980210d9a43ce8cad978c157bda5e8ae9951538209268
\ No newline at end of file diff --git a/db/schema_migrations/20210329192716 b/db/schema_migrations/20210329192716 new file mode 100644 index 00000000000..8767c215335 --- /dev/null +++ b/db/schema_migrations/20210329192716 @@ -0,0 +1 @@ +d0f5341d76183882b68583bc012154566e99050c24a90c9b895d6863ad8f3273
\ No newline at end of file diff --git a/db/schema_migrations/20210331180118 b/db/schema_migrations/20210331180118 new file mode 100644 index 00000000000..45e62e7154d --- /dev/null +++ b/db/schema_migrations/20210331180118 @@ -0,0 +1 @@ +d8a17ce963801559292265dd0a997d8dbc69d2fa8b8840622490f878bf1eaa6a
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index c1a2652ff12..0b7c723f1f5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17323,7 +17323,8 @@ CREATE TABLE saml_providers ( enforced_sso boolean DEFAULT false NOT NULL, enforced_group_managed_accounts boolean DEFAULT false NOT NULL, prohibited_outer_forks boolean DEFAULT true NOT NULL, - default_membership_role smallint DEFAULT 10 NOT NULL + default_membership_role smallint DEFAULT 10 NOT NULL, + git_check_enforced boolean DEFAULT false NOT NULL ); CREATE SEQUENCE saml_providers_id_seq @@ -21762,6 +21763,8 @@ CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_ev CREATE INDEX idx_audit_events_part_on_entity_id_desc_author_id_created_at ON ONLY audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at); +CREATE INDEX idx_award_emoji_on_user_emoji_name_awardable_type_awardable_id ON award_emoji USING btree (user_id, name, awardable_type, awardable_id); + CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1); CREATE INDEX idx_container_exp_policies_on_project_id_next_run_at_enabled ON container_expiration_policies USING btree (project_id, next_run_at, enabled); @@ -22004,8 +22007,6 @@ CREATE INDEX index_authentication_events_on_user_id ON authentication_events USI CREATE INDEX index_award_emoji_on_awardable_type_and_awardable_id ON award_emoji USING btree (awardable_type, awardable_id); -CREATE INDEX index_award_emoji_on_user_id_and_name ON award_emoji USING btree (user_id, name); - CREATE UNIQUE INDEX index_aws_roles_on_role_external_id ON aws_roles USING btree (role_external_id); CREATE UNIQUE INDEX index_aws_roles_on_user_id ON aws_roles USING btree (user_id); diff --git a/doc/administration/nfs.md b/doc/administration/nfs.md index 7c80a2a3fe7..d04a86ab4c6 100644 --- a/doc/administration/nfs.md +++ b/doc/administration/nfs.md @@ -189,6 +189,7 @@ Note there are several options that you should consider using: | `nofail` | Don't halt boot process waiting for this mount to become available | `lookupcache=positive` | Tells the NFS client to honor `positive` cache results but invalidates any `negative` cache results. Negative cache results cause problems with Git. Specifically, a `git push` can fail to register uniformly across all NFS clients. The negative cache causes the clients to 'remember' that the files did not exist previously. | `hard` | Instead of `soft`. [Further details](#soft-mount-option). +| `cto` | `cto` is the default option, which you should use. Do not use `nocto`. [Further details](#nocto-mount-option). #### `soft` mount option @@ -225,6 +226,25 @@ the mount point. Use `SIGKILL` (`kill -9`) to deal with hung processes. The `intr` option [stopped working in the 2.6 kernel](https://access.redhat.com/solutions/157873). +#### `nocto` mount option + +Do not use `nocto`. Instead, use `cto`, which is the default. + +When using `nocto`, the dentry cache is always used, up to `acdirmax` seconds (attribute cache time) from the time it's created. + +This results in stale dentry cache issues with multiple clients, where each client can see a different (cached) +version of a directory. + +From the [Linux man page](https://linux.die.net/man/5/nfs), the important parts: + +> If the nocto option is specified, the client uses a non-standard heuristic to determine when files on the server have changed. +> +> Using the nocto option may improve performance for read-only mounts, but should be used only if the data on the server changes only occasionally. + +We have noticed this behavior in an issue about [refs not found after a push](https://gitlab.com/gitlab-org/gitaly/-/issues/2589), +where newly added loose refs can be seen as missing on a different client with a local dentry cache, as +[described in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/326066#note_539436931). + ### A single NFS mount It's recommended to nest all GitLab data directories within a mount, that allows automatic diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index 8bac02c99af..7e4e7007ca2 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -392,6 +392,28 @@ field :blob, type: Types::Snippets::BlobType, This will increment the [`complexity` score](#field-complexity) of the field by `1`. +If a resolver calls Gitaly, it can be annotated with +`BaseResolver.calls_gitaly!`. This passes `calls_gitaly: true` to any +field that uses this resolver. + +For example: + +```ruby +class BranchResolver < BaseResolver + type ::Types::BranchType, null: true + calls_gitaly! + + argument name: ::GraphQL::STRING_TYPE, required: true + + def resolve(name:) + object.branch(name) + end +end +``` + +Then when we use it, any field that uses `BranchResolver` has the correct +value for `calls_gitaly:`. + ### Exposing permissions for a type To expose permissions the current user has on a resource, you can call @@ -1137,9 +1159,10 @@ When using resolvers, they can and should serve as the SSoT for field metadata. All field options (apart from the field name) can be declared on the resolver. These include: -- `type` (this is particularly important, and is planned to be mandatory) +- `type` (required - all resolvers must include a type annotation) - `extras` - `description` +- Gitaly annotations (with `calls_gitaly!`) Example: @@ -1149,6 +1172,7 @@ module Resolvers type Types::MyType, null: true extras [:lookahead] description 'Retrieve a single MyType' + calls_gitaly! end end ``` diff --git a/doc/development/code_review.md b/doc/development/code_review.md index f354d7ccbe9..03ebd333e28 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -252,7 +252,7 @@ After merging, a maintainer should stay as the reviewer listed on the merge requ ### Dogfooding the Reviewers feature -In March 18th 2021, an updated process was put in place aimed at efficiently and consistently dogfooding the Reviewers feature. +On March 18th 2021, an updated process was put in place aimed at efficiently and consistently dogfooding the Reviewers feature. Here is a summary of the changes, also reflected in this section above. @@ -409,6 +409,8 @@ When ready to merge: - **Start a new merge request pipeline with the `Run Pipeline` button in the merge request's "Pipelines" tab, and enable "Merge When Pipeline Succeeds" (MWPS).** Note that: - If **[master is broken](https://about.gitlab.com/handbook/engineering/workflow/#broken-master), + do not merge the merge request** except for + [very specific cases](https://about.gitlab.com/handbook/engineering/workflow/#criteria-for-merging-during-broken-master). For other cases, follow these [handbook instructions](https://about.gitlab.com/handbook/engineering/workflow/#merging-during-broken-master). - If the **latest [Pipeline for Merged Results](../ci/merge_request_pipelines/pipelines_for_merged_results/#pipelines-for-merged-results)** finished less than 2 hours ago, you might merge without starting a new pipeline as the merge request is close diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md index 8d564078ea2..33bfc040bb6 100644 --- a/doc/development/documentation/structure.md +++ b/doc/development/documentation/structure.md @@ -113,7 +113,7 @@ To create an issue: 1. Go to **Issues > List**. 1. In the top right, click **New issue**. 1. Complete the fields. (If you have a reference topic that lists each field, link to it here.) -1. Click **Submit issue**. +1. Click **Create issue**. The issue is created. You can view it by going to **Issues > List**. ``` diff --git a/doc/development/snowplow.md b/doc/development/snowplow.md index 3d566100677..720e2853851 100644 --- a/doc/development/snowplow.md +++ b/doc/development/snowplow.md @@ -314,6 +314,7 @@ Custom event tracking and instrumentation can be added by directly calling the ` | `project` | Project | nil | The project associated with the event. | | `user` | User | nil | The user associated with the event. | | `namespace` | Namespace | nil | The namespace associated with the event. | +| `extra` | Hash | `{}` | Additional keyword arguments are collected into a hash and sent with the event. | Tracking can be viewed as either tracking user behavior, or can be used for instrumentation to monitor and visualize performance over time in an area or aspect of code. @@ -495,6 +496,7 @@ The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/g | `namespace_id` | **{dotted-circle}** | integer | | | `environment` | **{check-circle}** | string (max 32 chars) | Name of the source environment, such as `production` or `staging` | | `source` | **{check-circle}** | string (max 32 chars) | Name of the source application, such as `gitlab-rails` or `gitlab-javascript` | +| `extra` | **{dotted-circle}** | JSON | Any additional data associated with the event, in the form of key-value pairs | ### Default Schema diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md index e4d9a87e39d..078a1a0be08 100644 --- a/doc/operations/incident_management/incidents.md +++ b/doc/operations/incident_management/incidents.md @@ -39,7 +39,7 @@ Incident, you have two options to do this manually. 1. Go to **Issues > List**, and select **New issue**. 1. In the **Type** dropdown, select **Incident**. Only fields relevant to incidents are displayed on the page. -1. Create the incident as needed, and select **Submit issue** to save the +1. Create the incident as needed, and select **Create issue** to save the incident. ![Incident List Create](img/new_incident_create_v13_4.png) diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 9020c6e0b91..f622d075ba4 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -119,7 +119,7 @@ the unresolved threads. ![Issue mentioning threads in a merge request](img/preview_issue_for_threads.png) -Hitting **Submit issue** causes all threads to be marked as resolved and +Hitting **Create issue** causes all threads to be marked as resolved and add a note referring to the newly created issue. ![Mark threads as resolved notice](img/resolve_thread_issue_notice.png) diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 6078de0f9e1..da7e9828d48 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -95,6 +95,7 @@ Please note that the certificate [fingerprint algorithm](../../../integration/sa - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/9255) in GitLab 11.11 with ongoing enforcement in the GitLab UI. - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/292811) in GitLab 13.8, with an updated timeout experience. - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/211962) in GitLab 13.8 with allowing group owners to not go through SSO. +- [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/9152) in GitLab 13.11 with enforcing open SSO session to use Git if this setting is switched on. With this option enabled, users must go through your group's GitLab single sign-on URL if they wish to access group resources through the UI. Users can't be manually added as members. @@ -104,9 +105,15 @@ However, users are not prompted to sign in through SSO on each visit. GitLab che has authenticated through SSO. If it's been more than 1 day since the last sign-in, GitLab prompts the user to sign in again through SSO. -We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152). +We intend to add a similar SSO requirement for [API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152). -When SSO enforcement is enabled for a group, users can't share a project in the group outside the top-level group, even if the project is forked. +SSO has the following effects when enabled: + +- For groups, users can't share a project in the group outside the top-level group, + even if the project is forked. +- For a Git activity, users must be signed-in through SSO before they can push to or + pull from a GitLab repository. +<!-- Add bullet for API activity when https://gitlab.com/gitlab-org/gitlab/-/issues/9152 is complete --> ## Providers diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md index e1918b68ddc..25357a1db0b 100644 --- a/doc/user/project/issues/confidential_issues.md +++ b/doc/user/project/issues/confidential_issues.md @@ -19,7 +19,7 @@ You can make an issue confidential during issue creation or by editing an existing one. When you create a new issue, a checkbox right below the text area is available -to mark the issue as confidential. Check that box and hit the **Submit issue** +to mark the issue as confidential. Check that box and hit the **Create issue** button to create the issue. For existing issues, edit them, check the confidential checkbox and hit **Save changes**. diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index 277238bfc98..3c3de26d7dd 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -295,5 +295,5 @@ The contents of the public directory can be confirmed by [browsing the artifacts Files listed under the public directory can be accessed through the Pages URL for the project. A 404 can also be related to incorrect permissions. If [Pages Access Control](pages_access_control.md) is enabled, and a user -navigates to the Pages URL and receives a 404 reponse, it is possible that the user does not have permission to view the site. +navigates to the Pages URL and receives a 404 response, it is possible that the user does not have permission to view the site. To fix this, verify that the user is a member of the project. diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index c5ca46827cb..31e4755192e 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -91,6 +91,7 @@ module Gitlab when *PUSH_COMMANDS check_push_access! end + check_additional_conditions! success_result end @@ -530,6 +531,10 @@ module Gitlab def size_checker container.repository_size_checker end + + # overriden in EE + def check_additional_conditions! + end end end diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb index 2f0e7e99305..3f2ff4ce4ac 100644 --- a/lib/gitlab/tracking.rb +++ b/lib/gitlab/tracking.rb @@ -9,8 +9,8 @@ module Gitlab Gitlab::CurrentSettings.snowplow_enabled? end - def event(category, action, label: nil, property: nil, value: nil, context: [], project: nil, user: nil, namespace: nil) # rubocop:disable Metrics/ParameterLists - contexts = [Tracking::StandardContext.new(project: project, user: user, namespace: namespace).to_context, *context] + def event(category, action, label: nil, property: nil, value: nil, context: [], project: nil, user: nil, namespace: nil, **extra) # rubocop:disable Metrics/ParameterLists + contexts = [Tracking::StandardContext.new(project: project, user: user, namespace: namespace, **extra).to_context, *context] snowplow.event(category, action, label: label, property: property, value: value, context: contexts) product_analytics.event(category, action, label: label, property: property, value: value, context: contexts) diff --git a/lib/gitlab/tracking/standard_context.rb b/lib/gitlab/tracking/standard_context.rb index 5880116132a..da030649f76 100644 --- a/lib/gitlab/tracking/standard_context.rb +++ b/lib/gitlab/tracking/standard_context.rb @@ -3,11 +3,11 @@ module Gitlab module Tracking class StandardContext - GITLAB_STANDARD_SCHEMA_URL = 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-3' + GITLAB_STANDARD_SCHEMA_URL = 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-4' GITLAB_RAILS_SOURCE = 'gitlab-rails' - def initialize(namespace: nil, project: nil, user: nil, **data) - @data = data + def initialize(namespace: nil, project: nil, user: nil, **extra) + @extra = extra end def to_context @@ -35,8 +35,9 @@ module Gitlab def to_h { environment: environment, - source: source - }.merge(@data) + source: source, + extra: @extra + } end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 72f5c58d8ed..1afde61f340 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2076,6 +2076,9 @@ msgstr "" msgid "Additional text" msgstr "" +msgid "Address" +msgstr "" + msgid "Adds" msgstr "" @@ -4943,9 +4946,6 @@ msgstr "" msgid "Boards|An error occurred while fetching group projects. Please try again." msgstr "" -msgid "Boards|An error occurred while fetching issues. Please reload the page." -msgstr "" - msgid "Boards|An error occurred while fetching labels. Please reload the page." msgstr "" @@ -5694,6 +5694,9 @@ msgstr "" msgid "Change this value to influence how frequently the GitLab UI polls for updates." msgstr "" +msgid "Change this value to influence how frequently the GitLab UI polls for updates. If you set the value to 2 all polling intervals are multiplied by 2, which means that polling happens half as frequently. The multiplier can also have a decimal value. The default value (1) is a reasonable choice for the majority of GitLab installations. Set to 0 to completely disable polling." +msgstr "" + msgid "Change title" msgstr "" @@ -6429,12 +6432,39 @@ msgstr "" msgid "CloudLicense|Activate" msgstr "" +msgid "CloudLicense|ID" +msgstr "" + +msgid "CloudLicense|Last Sync" +msgstr "" + +msgid "CloudLicense|Licensed to" +msgstr "" + +msgid "CloudLicense|Manage" +msgstr "" + msgid "CloudLicense|Paste your activation code" msgstr "" msgid "CloudLicense|Paste your activation code below" msgstr "" +msgid "CloudLicense|Plan" +msgstr "" + +msgid "CloudLicense|Renews" +msgstr "" + +msgid "CloudLicense|Started" +msgstr "" + +msgid "CloudLicense|Subscription details" +msgstr "" + +msgid "CloudLicense|Sync Subscription details" +msgstr "" + msgid "CloudLicense|This instance is currently using the %{planName} plan." msgstr "" @@ -7763,6 +7793,9 @@ msgstr "" msgid "Community forum" msgstr "" +msgid "Company" +msgstr "" + msgid "Company name" msgstr "" @@ -9170,6 +9203,9 @@ msgstr "" msgid "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available." msgstr "" +msgid "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance." +msgstr "" + msgid "Creation date" msgstr "" @@ -11511,6 +11547,9 @@ msgstr "" msgid "Enable Auto DevOps" msgstr "" +msgid "Enable Git pack file bitmap creation" +msgstr "" + msgid "Enable Gitpod" msgstr "" @@ -11535,6 +11574,9 @@ msgstr "" msgid "Enable Pseudonymizer data collection" msgstr "" +msgid "Enable Repository Checks" +msgstr "" + msgid "Enable SSL verification" msgstr "" @@ -11559,6 +11601,9 @@ msgstr "" msgid "Enable and disable Service Desk. Some additional configuration might be required. %{link_start}Learn more%{link_end}." msgstr "" +msgid "Enable automatic repository housekeeping (git repack, git gc)" +msgstr "" + msgid "Enable classification control using an external service" msgstr "" @@ -14195,6 +14240,9 @@ msgstr "" msgid "Git" msgstr "" +msgid "Git GC period" +msgstr "" + msgid "Git LFS is not enabled on this GitLab server, contact your admin." msgstr "" @@ -14327,6 +14375,9 @@ msgstr "" msgid "GitLab version" msgstr "" +msgid "GitLab will periodically run %{link_to_git_fsck} in all project and wiki repositories to look for silent disk corruption issues." +msgstr "" + msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory." msgstr "" @@ -14906,9 +14957,15 @@ msgstr "" msgid "GroupSAML|Are you sure you want to remove the SAML group link?" msgstr "" +msgid "GroupSAML|Before enforcing SSO, enable SAML authentication." +msgstr "" + msgid "GroupSAML|Certificate fingerprint" msgstr "" +msgid "GroupSAML|Check SSO on git activity" +msgstr "" + msgid "GroupSAML|Configuration" msgstr "" @@ -14924,7 +14981,10 @@ msgstr "" msgid "GroupSAML|Enable SAML authentication for this group." msgstr "" -msgid "GroupSAML|Enforce SSO-only authentication for this group." +msgid "GroupSAML|Enforce SSO-access for Git in this group." +msgstr "" + +msgid "GroupSAML|Enforce SSO-only authentication for web activity for this group." msgstr "" msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group." @@ -15020,9 +15080,6 @@ msgstr "" msgid "GroupSAML|This will be set as the access level of users added to the group." msgstr "" -msgid "GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication." -msgstr "" - msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO." msgstr "" @@ -15699,6 +15756,12 @@ msgstr "" msgid "If you did not recently sign in, you should immediately change your password: %{password_link}." msgstr "" +msgid "If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database." +msgstr "" + +msgid "If you keep automatic housekeeping disabled for a long time Git repository access on your GitLab server will become slower and your repositories will use more disk space. We recommend to always leave this enabled." +msgstr "" + msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes." msgstr "" @@ -16509,6 +16572,9 @@ msgstr "" msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}." msgstr "" +msgid "Infrastructure Registry" +msgstr "" + msgid "Inherited" msgstr "" @@ -21351,6 +21417,15 @@ msgstr "" msgid "Number of Elasticsearch shards" msgstr "" +msgid "Number of Git pushes after which 'git gc' is run." +msgstr "" + +msgid "Number of Git pushes after which a full 'git repack' is run." +msgstr "" + +msgid "Number of Git pushes after which an incremental 'git repack' is run." +msgstr "" + msgid "Number of LOCs per commit" msgstr "" @@ -23147,6 +23222,9 @@ msgstr "" msgid "Policy project doesn't exist" msgstr "" +msgid "Polling interval multiplier" +msgstr "" + msgid "Popularity" msgstr "" @@ -26033,6 +26111,9 @@ msgstr "" msgid "Repository check was triggered." msgstr "" +msgid "Repository checks" +msgstr "" + msgid "Repository cleanup" msgstr "" @@ -29319,9 +29400,6 @@ msgstr "" msgid "Submit feedback" msgstr "" -msgid "Submit issue" -msgstr "" - msgid "Submit review" msgstr "" diff --git a/scripts/gitaly-test-build b/scripts/gitaly-test-build index bb561e2906a..849c08df527 100755 --- a/scripts/gitaly-test-build +++ b/scripts/gitaly-test-build @@ -13,6 +13,8 @@ class GitalyTestBuild include GitalyTest def run + set_bundler_config + abort 'gitaly build failed' unless build_gitaly ensure_gitlab_shell_secret! diff --git a/scripts/gitaly-test-spawn b/scripts/gitaly-test-spawn index 8547d0b13e4..7cb9ea803f8 100755 --- a/scripts/gitaly-test-spawn +++ b/scripts/gitaly-test-spawn @@ -9,6 +9,7 @@ class GitalyTestSpawn include GitalyTest def run + set_bundler_config install_gitaly_gems if ENV['CI'] check_gitaly_config! diff --git a/scripts/gitaly_test.rb b/scripts/gitaly_test.rb index 7ce1d7b16e4..f970457fea7 100644 --- a/scripts/gitaly_test.rb +++ b/scripts/gitaly_test.rb @@ -34,6 +34,10 @@ module GitalyTest File.join(tmp_tests_gitaly_dir, 'ruby', 'Gemfile') end + def gemfile_dir + File.dirname(gemfile) + end + def gitlab_shell_secret_file File.join(tmp_tests_gitlab_shell_dir, '.gitlab_shell_secret') end @@ -42,8 +46,7 @@ module GitalyTest env_hash = { 'HOME' => File.expand_path('tmp/tests'), 'GEM_PATH' => Gem.path.join(':'), - 'BUNDLE_APP_CONFIG' => File.join(File.dirname(gemfile), '.bundle/config'), - 'BUNDLE_FLAGS' => "--jobs=4 --retry=3", + 'BUNDLE_APP_CONFIG' => File.join(gemfile_dir, '.bundle'), 'BUNDLE_INSTALL_FLAGS' => nil, 'BUNDLE_GEMFILE' => gemfile, 'RUBYOPT' => nil, @@ -52,13 +55,20 @@ module GitalyTest 'GITALY_TESTING_NO_GIT_HOOKS' => "1" } + env_hash + end + + # rubocop:disable GitlabSecurity/SystemCommandInjection + def set_bundler_config + system('bundle config set --local jobs 4', chdir: gemfile_dir) + system('bundle config set --local retry 3', chdir: gemfile_dir) + if ENV['CI'] bundle_path = File.expand_path('../vendor/gitaly-ruby', __dir__) - env_hash['BUNDLE_FLAGS'] += " --path=#{bundle_path}" + system('bundle', 'config', 'set', '--local', 'path', bundle_path, chdir: gemfile_dir) end - - env_hash end + # rubocop:enable GitlabSecurity/SystemCommandInjection def config_path(service) case service diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 4d419f89aa3..feb0b0b992f 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -60,7 +60,7 @@ RSpec.describe 'Issue Boards new issue', :js do page.within(first('.board-new-issue-form')) do find('.form-control').set('bug') - click_button 'Submit issue' + click_button 'Create issue' end wait_for_requests @@ -85,7 +85,7 @@ RSpec.describe 'Issue Boards new issue', :js do page.within(first('.board-new-issue-form')) do find('.form-control').set('bug') - click_button 'Submit issue' + click_button 'Create issue' end wait_for_requests @@ -100,7 +100,7 @@ RSpec.describe 'Issue Boards new issue', :js do page.within(first('.board-new-issue-form')) do find('.form-control').set('new issue') - click_button 'Submit issue' + click_button 'Create issue' end wait_for_requests diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 57e88fac013..977147c3c6b 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -7,10 +7,10 @@ RSpec.describe 'Project issue boards sidebar', :js do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } - let_it_be(:issue) { create(:issue, project: project, relative_position: 1) } let_it_be(:board) { create(:board, project: project) } let_it_be(:list) { create(:list, board: board, position: 0) } - let(:card) { find('.board:nth-child(1)').first('.board-card') } + + let_it_be(:issue, reload: true) { create(:issue, project: project, relative_position: 1) } before do project.add_maintainer(user) @@ -18,41 +18,17 @@ RSpec.describe 'Project issue boards sidebar', :js do sign_in(user) visit project_board_path(project, board) - wait_for_requests - end - - it 'shows sidebar when clicking issue' do - click_card(card) - - expect(page).to have_selector('.issue-boards-sidebar') - end - - it 'closes sidebar when clicking issue' do - click_card(card) - expect(page).to have_selector('.issue-boards-sidebar') - - click_card(card) - - expect(page).not_to have_selector('.issue-boards-sidebar') + wait_for_requests end - it 'closes sidebar when clicking close button' do - click_card(card) + it_behaves_like 'issue boards sidebar' - expect(page).to have_selector('.issue-boards-sidebar') - - find("[data-testid='sidebar-drawer'] .gl-drawer-close-button").click - - expect(page).not_to have_selector('.issue-boards-sidebar') + def first_card + find('.board:nth-child(1)').first("[data-testid='board_card']") end - it 'shows issue details when sidebar is open' do - click_card(card) - - page.within('.issue-boards-sidebar') do - expect(page).to have_content(issue.title) - expect(page).to have_content(issue.to_reference) - end + def click_first_issue_card + click_card(first_card) end end diff --git a/spec/features/boards/sidebar_subscription_spec.rb b/spec/features/boards/sidebar_subscription_spec.rb deleted file mode 100644 index 598fec7514e..00000000000 --- a/spec/features/boards/sidebar_subscription_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Project issue boards sidebar subscription', :js do - include BoardHelpers - - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } - let_it_be(:issue1) { create(:issue, project: project, relative_position: 1) } - let_it_be(:issue2) { create(:issue, project: project, relative_position: 2) } - let_it_be(:subscription) { create(:subscription, user: user, project: project, subscribable: issue2, subscribed: true) } - let_it_be(:board) { create(:board, project: project) } - let_it_be(:list) { create(:list, board: board, position: 0) } - let(:card1) { find('.board:nth-child(1) .board-card:nth-of-type(1)') } - let(:card2) { find('.board:nth-child(1) .board-card:nth-of-type(2)') } - - before do - stub_feature_flags(graphql_board_lists: false) - - project.add_maintainer(user) - - sign_in(user) - - visit project_board_path(project, board) - wait_for_requests - end - - context 'subscription' do - it 'changes issue subscription' do - click_card(card1) - wait_for_requests - - page.within('.subscriptions') do - find('[data-testid="subscription-toggle"] button:not(.is-checked)').click - wait_for_requests - - expect(page).to have_css('[data-testid="subscription-toggle"] button.is-checked') - end - end - - it 'has checked subscription toggle when already subscribed' do - click_card(card2) - wait_for_requests - - page.within('.subscriptions') do - find('[data-testid="subscription-toggle"] button.is-checked').click - wait_for_requests - - expect(page).to have_css('[data-testid="subscription-toggle"] button:not(.is-checked)') - end - end - end -end diff --git a/spec/features/boards/sidebar_time_tracking_spec.rb b/spec/features/boards/sidebar_time_tracking_spec.rb deleted file mode 100644 index 3ac8b93692a..00000000000 --- a/spec/features/boards/sidebar_time_tracking_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Project issue boards sidebar time tracking', :js do - include BoardHelpers - - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } - let_it_be(:board) { create(:board, project: project) } - let_it_be(:list) { create(:list, board: board, position: 0) } - let!(:issue) { create(:issue, project: project, relative_position: 1) } - let(:card) { find('.board:nth-child(1)').first('.board-card') } - - let(:application_settings) { {} } - - before do - stub_feature_flags(graphql_board_lists: false) - - project.add_maintainer(user) - - sign_in(user) - - stub_application_setting(application_settings) - - visit project_board_path(project, board) - wait_for_requests - end - - context 'time tracking' do - let(:compare_meter_tooltip) { find('.time-tracking .time-tracking-content .compare-meter')['title'] } - - before do - issue.timelogs.create!(time_spent: 14400, user: user) - issue.update!(time_estimate: 128800) - - click_card(card) - end - - it 'shows time tracking progress bar' do - expect(compare_meter_tooltip).to eq('Time remaining: 3d 7h 46m') - end - - context 'when time_tracking_limit_to_hours is true' do - let(:application_settings) { { time_tracking_limit_to_hours: true } } - - it 'shows time tracking progress bar' do - expect(compare_meter_tooltip).to eq('Time remaining: 31h 46m') - end - end - end -end diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index aab3f5e68d5..b4c60ff4fa3 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Group Boards' do find('.gl-new-dropdown-item button').click end - click_button 'Submit issue' + click_button 'Create issue' expect(page).to have_content(issue_title) end diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb index 710755ce306..021b1af54d4 100644 --- a/spec/features/groups/navbar_spec.rb +++ b/spec/features/groups/navbar_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Group navbar' do before do stub_config(registry: { enabled: true }) - insert_container_nav(_('Kubernetes')) + insert_container_nav visit group_path(group) end @@ -80,7 +80,7 @@ RSpec.describe 'Group navbar' do before do stub_config(dependency_proxy: { enabled: true }) - insert_dependency_proxy_nav(_('Dependency Proxy')) + insert_dependency_proxy_nav visit group_path(group) end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 4ff3827b240..7dc3ee63669 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -13,6 +13,8 @@ RSpec.describe 'Project navbar' do before do insert_package_nav(_('Operations')) + insert_infrastructure_registry_nav + stub_config(registry: { enabled: false }) project.add_maintainer(user) sign_in(user) @@ -60,7 +62,7 @@ RSpec.describe 'Project navbar' do before do stub_config(registry: { enabled: true }) - insert_container_nav(_('Operations')) + insert_container_nav visit project_path(project) end diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb index 7500f2fe59a..8ba79d77c22 100644 --- a/spec/features/users/terms_spec.rb +++ b/spec/features/users/terms_spec.rb @@ -121,7 +121,7 @@ RSpec.describe 'Users > Terms' do enforce_terms - click_button 'Submit issue' + click_button 'Create issue' expect(current_path).to eq(terms_path) diff --git a/spec/frontend/boards/board_new_issue_deprecated_spec.js b/spec/frontend/boards/board_new_issue_deprecated_spec.js index 3903ad201b2..3beaf870bf5 100644 --- a/spec/frontend/boards/board_new_issue_deprecated_spec.js +++ b/spec/frontend/boards/board_new_issue_deprecated_spec.js @@ -111,7 +111,7 @@ describe('Issue boards new issue form', () => { describe('submit success', () => { it('creates new issue', () => { - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); return Vue.nextTick() .then(submitIssue) @@ -122,7 +122,7 @@ describe('Issue boards new issue form', () => { it('enables button after submit', () => { jest.spyOn(wrapper.vm, 'submit').mockImplementation(); - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); return Vue.nextTick() .then(submitIssue) @@ -132,7 +132,7 @@ describe('Issue boards new issue form', () => { }); it('clears title after submit', () => { - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); return Vue.nextTick() .then(submitIssue) @@ -143,17 +143,17 @@ describe('Issue boards new issue form', () => { it('sets detail issue after submit', () => { expect(boardsStore.detail.issue.title).toBe(undefined); - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); return Vue.nextTick() .then(submitIssue) .then(() => { - expect(boardsStore.detail.issue.title).toBe('submit issue'); + expect(boardsStore.detail.issue.title).toBe('create issue'); }); }); it('sets detail list after submit', () => { - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); return Vue.nextTick() .then(submitIssue) @@ -164,7 +164,7 @@ describe('Issue boards new issue form', () => { it('sets detail weight after submit', () => { boardsStore.weightFeatureAvailable = true; - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); return Vue.nextTick() .then(submitIssue) @@ -175,7 +175,7 @@ describe('Issue boards new issue form', () => { it('does not set detail weight after submit', () => { boardsStore.weightFeatureAvailable = false; - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); return Vue.nextTick() .then(submitIssue) diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js index 737a18294bc..e6405bbcff3 100644 --- a/spec/frontend/boards/components/board_new_issue_spec.js +++ b/spec/frontend/boards/components/board_new_issue_spec.js @@ -86,7 +86,7 @@ describe('Issue boards new issue form', () => { describe('submit success', () => { it('creates new issue', async () => { - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); await vm.$nextTick(); await submitIssue(); @@ -95,7 +95,7 @@ describe('Issue boards new issue form', () => { it('enables button after submit', async () => { jest.spyOn(wrapper.vm, 'submit').mockImplementation(); - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); await vm.$nextTick(); await submitIssue(); @@ -103,7 +103,7 @@ describe('Issue boards new issue form', () => { }); it('clears title after submit', async () => { - wrapper.setData({ title: 'submit issue' }); + wrapper.setData({ title: 'create issue' }); await vm.$nextTick(); await submitIssue(); diff --git a/spec/frontend/members/components/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js index 3f4d9155c5d..5cf3a4cdc13 100644 --- a/spec/frontend/members/components/avatars/user_avatar_spec.js +++ b/spec/frontend/members/components/avatars/user_avatar_spec.js @@ -1,31 +1,25 @@ import { GlAvatarLink, GlBadge } from '@gitlab/ui'; import { within } from '@testing-library/dom'; import { mount, createWrapper } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; import UserAvatar from '~/members/components/avatars/user_avatar.vue'; import { member as memberMock, member2faEnabled, orphanedMember } from '../../mock_data'; -Vue.use(Vuex); - describe('UserAvatar', () => { let wrapper; const { user } = memberMock; - const createComponent = (propsData = {}, state = {}) => { + const createComponent = (propsData = {}, provide = {}) => { wrapper = mount(UserAvatar, { propsData: { member: memberMock, isCurrentUser: false, ...propsData, }, - store: new Vuex.Store({ - state: { - canManageMembers: true, - ...state, - }, - }), + provide: { + canManageMembers: true, + ...provide, + }, }); }; diff --git a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js index 14b437a8c4e..326a29747ef 100644 --- a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js +++ b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js @@ -10,10 +10,9 @@ localVue.use(Vuex); describe('MembersFilteredSearchBar', () => { let wrapper; - const createComponent = (state) => { + const createComponent = ({ state = {}, provide = {} } = {}) => { const store = new Vuex.Store({ state: { - sourceId: 1, filteredSearchBar: { show: true, tokens: ['two_factor'], @@ -21,13 +20,17 @@ describe('MembersFilteredSearchBar', () => { placeholder: 'Filter members', recentSearchesStorageKey: 'group_members', }, - canManageMembers: true, ...state, }, }); wrapper = shallowMount(MembersFilteredSearchBar, { localVue, + provide: { + sourceId: 1, + canManageMembers: true, + ...provide, + }, store, }); }; @@ -68,14 +71,18 @@ describe('MembersFilteredSearchBar', () => { describe('when `canManageMembers` is false', () => { it('excludes 2FA token', () => { createComponent({ - filteredSearchBar: { - show: true, - tokens: ['two_factor', 'with_inherited_permissions'], - searchParam: 'search', - placeholder: 'Filter members', - recentSearchesStorageKey: 'group_members', + state: { + filteredSearchBar: { + show: true, + tokens: ['two_factor', 'with_inherited_permissions'], + searchParam: 'search', + placeholder: 'Filter members', + recentSearchesStorageKey: 'group_members', + }, + }, + provide: { + canManageMembers: false, }, - canManageMembers: false, }); expect(findFilteredSearchBar().props('tokens')).toEqual([ diff --git a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js index 357fad741e9..390e12bc0e5 100644 --- a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js +++ b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js @@ -15,7 +15,6 @@ describe('SortDropdown', () => { const createComponent = (state) => { const store = new Vuex.Store({ state: { - sourceId: 1, tableSortableFields: ['account', 'granted', 'expires', 'maxRole', 'lastSignIn'], filteredSearchBar: { show: true, @@ -30,6 +29,9 @@ describe('SortDropdown', () => { wrapper = mount(SortDropdown, { localVue, + provide: { + sourceId: 1, + }, store, }); }; diff --git a/spec/frontend/members/components/table/members_table_cell_spec.js b/spec/frontend/members/components/table/members_table_cell_spec.js index b7dcd2a9fae..5375ee11736 100644 --- a/spec/frontend/members/components/table/members_table_cell_spec.js +++ b/spec/frontend/members/components/table/members_table_cell_spec.js @@ -42,21 +42,21 @@ describe('MembersTableCell', () => { const createStore = (state = {}) => { return new Vuex.Store({ - state: { - sourceId: 1, - currentUserId: 1, - ...state, - }, + state, }); }; let wrapper; - const createComponent = (propsData, state = {}) => { + const createComponent = (propsData, state) => { wrapper = mount(MembersTableCell, { localVue, propsData, store: createStore(state), + provide: { + sourceId: 1, + currentUserId: 1, + }, scopedSlots: { default: ` <wrapped-component diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js index cf5811e72e7..0395dc39880 100644 --- a/spec/frontend/members/components/table/members_table_spec.js +++ b/spec/frontend/members/components/table/members_table_spec.js @@ -32,17 +32,20 @@ describe('MembersTable', () => { table: { 'data-qa-selector': 'members_list' }, tr: { 'data-qa-selector': 'member_row' }, }, - sourceId: 1, - currentUserId: 1, ...state, }, }); }; - const createComponent = (state) => { + const createComponent = (state, provide = {}) => { wrapper = mount(MembersTable, { localVue, store: createStore(state), + provide: { + sourceId: 1, + currentUserId: 1, + ...provide, + }, stubs: [ 'member-avatar', 'member-source', @@ -119,7 +122,7 @@ describe('MembersTable', () => { describe('when user is not logged in', () => { it('does not render the "Actions" field', () => { - createComponent({ currentUserId: null, tableFields: ['actions'] }); + createComponent({ tableFields: ['actions'] }, { currentUserId: null }); expect(within(wrapper.element).queryByTestId('col-actions')).toBe(null); }); diff --git a/spec/frontend/members/index_spec.js b/spec/frontend/members/index_spec.js index dd3b9ddd912..f40c08401d4 100644 --- a/spec/frontend/members/index_spec.js +++ b/spec/frontend/members/index_spec.js @@ -42,33 +42,6 @@ describe('initMembersApp', () => { expect(wrapper.find(MembersApp).exists()).toBe(true); }); - it('sets `currentUserId` in Vuex store', () => { - setup(); - - expect(vm.$store.state.currentUserId).toBe(123); - }); - - describe('when `gon.current_user_id` is not set (user is not logged in)', () => { - it('sets `currentUserId` as `null` in Vuex store', () => { - window.gon = {}; - setup(); - - expect(vm.$store.state.currentUserId).toBeNull(); - }); - }); - - it('parses and sets `data-source-id` as `sourceId` in Vuex store', () => { - setup(); - - expect(vm.$store.state.sourceId).toBe(234); - }); - - it('parses and sets `data-can-manage-members` as `canManageMembers` in Vuex store', () => { - setup(); - - expect(vm.$store.state.canManageMembers).toBe(true); - }); - it('parses and sets `members` in Vuex store', () => { setup(); diff --git a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb index 2a4478ded6b..24f5299d357 100644 --- a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb +++ b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb @@ -23,10 +23,10 @@ RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do end it 'merges the context payload into event payload', :aggregate_failures do - expect(result_hash[:user]).to eq(ip_address: '127.0.0.1', username: 'root') + expect(result_hash[:user]).to include(ip_address: '127.0.0.1', username: 'root') expect(result_hash[:tags]) - .to eq(priority: 'high', + .to include(priority: 'high', locale: 'en', program: 'test', feature_category: 'feature_a', diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb index 56074147854..9a4d7bd996e 100644 --- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb @@ -290,7 +290,7 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do subject { described_class.new(project) } before do - project.update(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git") + project.update!(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git") end it_behaves_like 'Gitlab::LegacyGithubImport::Importer#execute' do diff --git a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb index 4b1e0d2c144..454bab8846c 100644 --- a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb @@ -152,7 +152,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do context 'when importing a Gitea project' do before do - project.update(import_type: 'gitea') + project.update!(import_type: 'gitea') end it_behaves_like 'Gitlab::LegacyGithubImport::IssueFormatter#attributes' diff --git a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb index 148b59dedab..64fcc46d304 100644 --- a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb @@ -92,7 +92,7 @@ RSpec.describe Gitlab::LegacyGithubImport::MilestoneFormatter do let(:iid_attr) { :id } before do - project.update(import_type: 'gitea') + project.update!(import_type: 'gitea') end it_behaves_like 'Gitlab::LegacyGithubImport::MilestoneFormatter#attributes' diff --git a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb index 3e6b9340d0b..7d8875e36c3 100644 --- a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb @@ -260,7 +260,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do context 'when importing a Gitea project' do before do - project.update(import_type: 'gitea') + project.update!(import_type: 'gitea') end it_behaves_like 'Gitlab::LegacyGithubImport::PullRequestFormatter#attributes' diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb index 561edbd38f8..dacd08cf12b 100644 --- a/spec/lib/gitlab/tracking/standard_context_spec.rb +++ b/spec/lib/gitlab/tracking/standard_context_spec.rb @@ -58,10 +58,16 @@ RSpec.describe Gitlab::Tracking::StandardContext do end context 'with extra data' do - subject { described_class.new(foo: 'bar') } + subject { described_class.new(extra_key_1: 'extra value 1', extra_key_2: 'extra value 2') } - it 'creates a Snowplow context with the given data' do - expect(snowplow_context.to_json.dig(:data, :foo)).to eq('bar') + it 'includes extra data in `extra` hash' do + expect(snowplow_context.to_json.dig(:data, :extra)).to eq(extra_key_1: 'extra value 1', extra_key_2: 'extra value 2') + end + end + + context 'without extra data' do + it 'contains an empty `extra` hash' do + expect(snowplow_context.to_json.dig(:data, :extra)).to be_empty end end diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb index c1e69b94406..edc19df5e37 100644 --- a/spec/lib/gitlab/tracking_spec.rb +++ b/spec/lib/gitlab/tracking_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Gitlab::Tracking do expect(Gitlab::Tracking::StandardContext) .to receive(:new) - .with(project: project, user: user, namespace: namespace) + .with(project: project, user: user, namespace: namespace, extra_key_1: 'extra value 1', extra_key_2: 'extra value 2') .and_call_original expect_any_instance_of(klass).to receive(:event) do |_, category, action, args| @@ -66,7 +66,8 @@ RSpec.describe Gitlab::Tracking do end described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5, - context: [other_context], project: project, user: user, namespace: namespace) + context: [other_context], project: project, user: user, namespace: namespace, + extra_key_1: 'extra value 1', extra_key_2: 'extra value 2') end end diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index aced094e219..03412fe1b66 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -490,6 +490,36 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do { 'id' => job.id, 'name' => job.name, 'token' => job.token }, { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token }) end + + describe 'preloading job_artifacts_archive' do + context 'when the feature flag is disabled' do + before do + stub_feature_flags(preload_associations_jobs_request_api_endpoint: false) + end + + it 'queries the ci_job_artifacts table multiple times' do + expect { request_job }.to exceed_all_query_limit(1).for_model(::Ci::JobArtifact) + end + + it 'queries the ci_builds table more than five times' do + expect { request_job }.to exceed_all_query_limit(5).for_model(::Ci::Build) + end + end + + context 'when the feature flag is enabled' do + before do + stub_feature_flags(preload_associations_jobs_request_api_endpoint: true) + end + + it 'queries the ci_job_artifacts table once only' do + expect { request_job }.not_to exceed_all_query_limit(1).for_model(::Ci::JobArtifact) + end + + it 'queries the ci_builds table five times' do + expect { request_job }.not_to exceed_all_query_limit(5).for_model(::Ci::Build) + end + end + end end context 'when pipeline have jobs with artifacts' do diff --git a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb index 380e20905c6..cad3d2e3373 100644 --- a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb +++ b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb @@ -268,4 +268,49 @@ RSpec.describe MergeRequestPollCachedWidgetEntity do end end end + + describe 'merge_pipeline' do + it 'returns nil' do + expect(subject[:merge_pipeline]).to be_nil + end + + context 'when is merged' do + let(:resource) { create(:merged_merge_request, source_project: project, merge_commit_sha: project.commit.id) } + let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.target_branch, sha: resource.merge_commit_sha) } + + before do + project.add_maintainer(user) + end + + it 'returns merge_pipeline' do + pipeline.reload + pipeline_payload = + MergeRequests::PipelineEntity + .represent(pipeline, request: request) + .as_json + + expect(subject[:merge_pipeline]).to eq(pipeline_payload) + end + + context 'when user cannot read pipelines on target project' do + before do + project.add_guest(user) + end + + it 'returns nil' do + expect(subject[:merge_pipeline]).to be_nil + end + end + + context 'when merge_request_cached_merge_pipeline_serializer is disabled' do + before do + stub_feature_flags(merge_request_cached_merge_pipeline_serializer: false) + end + + it 'returns nil' do + expect(subject[:merge_pipeline]).to be_nil + end + end + end + end end diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb index 3bf4f613167..9618624f226 100644 --- a/spec/serializers/merge_request_poll_widget_entity_spec.rb +++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb @@ -6,9 +6,9 @@ RSpec.describe MergeRequestPollWidgetEntity do include ProjectForksHelper using RSpec::Parameterized::TableSyntax - let(:project) { create :project, :repository } - let(:resource) { create(:merge_request, source_project: project, target_project: project) } - let(:user) { create(:user) } + let_it_be(:project) { create :project, :repository } + let_it_be(:resource) { create(:merge_request, source_project: project, target_project: project) } + let_it_be(:user) { create(:user) } let(:request) { double('request', current_user: user, project: project) } @@ -22,20 +22,33 @@ RSpec.describe MergeRequestPollWidgetEntity do end describe 'merge_pipeline' do + before do + stub_feature_flags(merge_request_cached_merge_pipeline_serializer: false) + end + it 'returns nil' do expect(subject[:merge_pipeline]).to be_nil end context 'when is merged' do - let(:resource) { create(:merged_merge_request, source_project: project, merge_commit_sha: project.commit.id) } - let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.target_branch, sha: resource.merge_commit_sha) } + let_it_be(:resource) { create(:merged_merge_request, source_project: project, merge_commit_sha: project.commit.id) } + let_it_be(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.target_branch, sha: resource.merge_commit_sha) } before do project.add_maintainer(user) end + context 'when user cannot read pipelines on target project' do + before do + project.team.truncate + end + + it 'returns nil' do + expect(subject[:merge_pipeline]).to be_nil + end + end + it 'returns merge_pipeline' do - pipeline.reload pipeline_payload = MergeRequests::PipelineEntity .represent(pipeline, request: request) @@ -44,9 +57,9 @@ RSpec.describe MergeRequestPollWidgetEntity do expect(subject[:merge_pipeline]).to eq(pipeline_payload) end - context 'when user cannot read pipelines on target project' do + context 'when merge_request_cached_merge_pipeline_serializer is enabled' do before do - project.add_guest(user) + stub_feature_flags(merge_request_cached_merge_pipeline_serializer: true) end it 'returns nil' do diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb index e18a708e41c..826108a63a5 100644 --- a/spec/support/helpers/navbar_structure_helper.rb +++ b/spec/support/helpers/navbar_structure_helper.rb @@ -29,7 +29,7 @@ module NavbarStructureHelper ) end - def insert_container_nav(within) + def insert_container_nav insert_after_sub_nav_item( _('Package Registry'), within: _('Packages & Registries'), @@ -37,11 +37,19 @@ module NavbarStructureHelper ) end - def insert_dependency_proxy_nav(within) + def insert_dependency_proxy_nav insert_after_sub_nav_item( _('Package Registry'), within: _('Packages & Registries'), new_sub_nav_item_name: _('Dependency Proxy') ) end + + def insert_infrastructure_registry_nav + insert_after_sub_nav_item( + _('Package Registry'), + within: _('Packages & Registries'), + new_sub_nav_item_name: _('Infrastructure Registry') + ) + end end diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb index 7a66eff3a41..b48c7f905b2 100644 --- a/spec/support/matchers/exceed_query_limit.rb +++ b/spec/support/matchers/exceed_query_limit.rb @@ -20,6 +20,11 @@ module ExceedQueryLimitHelpers self end + def for_model(model) + table = model.table_name if model < ActiveRecord::Base + for_query(/(FROM|UPDATE|INSERT INTO|DELETE FROM)\s+"#{table}"/) + end + def show_common_queries @show_common_queries = true self diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb new file mode 100644 index 00000000000..558536a80c6 --- /dev/null +++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'issue boards sidebar' do + include MobileHelpers + + before do + first_card.click + end + + it 'shows sidebar when clicking issue' do + expect(page).to have_selector('.issue-boards-sidebar') + end + + it 'closes sidebar when clicking issue' do + expect(page).to have_selector('.issue-boards-sidebar') + + first_card.click + + expect(page).not_to have_selector('.issue-boards-sidebar') + end + + it 'shows issue details when sidebar is open', :aggregate_failures do + page.within('.issue-boards-sidebar') do + expect(page).to have_content(issue.title) + expect(page).to have_content(issue.to_reference) + end + end + + context 'when clicking close button' do + before do + find("[data-testid='sidebar-drawer'] .gl-drawer-close-button").click + end + + it 'unhighlights the active issue card' do + expect(first_card[:class]).not_to include('is-active') + expect(first_card[:class]).not_to include('multi-select') + end + + it 'closes sidebar when clicking close button' do + expect(page).not_to have_selector('.issue-boards-sidebar') + end + end + + context 'in notifications subscription' do + it 'displays notifications toggle', :aggregate_failures do + page.within('[data-testid="sidebar-notifications"]') do + expect(page).to have_selector('[data-testid="notification-subscribe-toggle"]') + expect(page).to have_content('Notifications') + expect(page).not_to have_content('Notifications have been disabled by the project or group owner') + end + end + + it 'shows toggle as on then as off as user toggles to subscribe and unsubscribe', :aggregate_failures do + toggle = find('[data-testid="notification-subscribe-toggle"]') + + toggle.click + + expect(toggle).to have_css("button.is-checked") + + toggle.click + + expect(toggle).not_to have_css("button.is-checked") + end + + context 'when notifications have been disabled' do + before do + project.update_attribute(:emails_disabled, true) + + refresh_and_click_first_card + end + + it 'displays a message that notifications have been disabled' do + page.within('[data-testid="sidebar-notifications"]') do + expect(page).not_to have_selector('[data-testid="notification-subscribe-toggle"]') + expect(page).to have_content('Notifications have been disabled by the project or group owner') + end + end + end + end + + context 'in time tracking' do + it 'displays time tracking feature with default message' do + page.within('[data-testid="time-tracker"]') do + expect(page).to have_content('Time tracking') + expect(page).to have_content('No estimate or time spent') + end + end + + context 'when only spent time is recorded' do + before do + issue.timelogs.create!(time_spent: 3600, user: user) + + refresh_and_click_first_card + end + + it 'shows the total time spent only' do + page.within('[data-testid="time-tracker"]') do + expect(page).to have_content('Spent: 1h') + expect(page).not_to have_content('Estimated') + end + end + end + + context 'when only estimated time is recorded' do + before do + issue.update!(time_estimate: 3600) + + refresh_and_click_first_card + end + + it 'shows the estimated time only', :aggregate_failures do + page.within('[data-testid="time-tracker"]') do + expect(page).to have_content('Estimated: 1h') + expect(page).not_to have_content('Spent') + end + end + end + + context 'when estimated and spent times are available' do + before do + issue.timelogs.create!(time_spent: 1800, user: user) + issue.update!(time_estimate: 3600) + + refresh_and_click_first_card + end + + it 'shows time tracking progress bar' do + page.within('[data-testid="time-tracker"]') do + expect(page).to have_selector('[data-testid="timeTrackingComparisonPane"]') + end + end + + it 'shows both estimated and spent time text', :aggregate_failures do + page.within('[data-testid="time-tracker"]') do + expect(page).to have_content('Spent 30m') + expect(page).to have_content('Est 1h') + end + end + end + + context 'when limitedToHours instance option is turned on' do + before do + # 3600+3600*24 = 1d 1h or 25h + issue.timelogs.create!(time_spent: 3600 + 3600 * 24, user: user) + stub_application_setting(time_tracking_limit_to_hours: true) + + refresh_and_click_first_card + end + + it 'shows the total time spent only' do + page.within('[data-testid="time-tracker"]') do + expect(page).to have_content('Spent: 25h') + end + end + end + end + + def refresh_and_click_first_card + page.refresh + + wait_for_requests + + first_card.click + end +end diff --git a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb index 6d8d9ba0754..67d87fe3c2f 100644 --- a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb +++ b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb @@ -225,6 +225,16 @@ RSpec.describe ExceedQueryLimitHelpers do expect(test_matcher.actual_count).to eq(2) end + it 'can filter specific models' do + test_matcher = TestMatcher.new.for_model(TestQueries) + test_matcher.verify_count do + TestQueries.first + TestQueries.connection.execute('select 1') + end + + expect(test_matcher.actual_count).to eq(1) + end + it 'can ignore specific queries' do test_matcher = TestMatcher.new.ignoring(/foobar/) test_matcher.verify_count do |