Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-01 12:08:56 +00:00
parent cfec4ed6fe
commit 50d66f5ece
100 changed files with 833 additions and 362 deletions

View File

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

View File

@ -1 +1 @@
c5786b09543e40acc6e05bd4d29f6d89106b8e8a
46db2b9e1da386cc081455eef16f5fa1a9fefa51

View File

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

View File

@ -127,7 +127,7 @@ export default {
</component>
<epics-swimlanes
v-else
v-else-if="boardListsToUse.length"
ref="swimlanes"
:lists="boardListsToUse"
:can-admin-list="canAdminList"

View File

@ -10,7 +10,7 @@ import ProjectSelect from './project_select.vue';
export default {
name: 'BoardNewIssue',
i18n: {
submit: __('Submit issue'),
submit: __('Create issue'),
cancel: __('Cancel'),
},
components: {

View File

@ -121,7 +121,7 @@ export default {
variant="success"
category="primary"
type="submit"
>{{ __('Submit issue') }}</gl-button
>{{ __('Create issue') }}</gl-button
>
<gl-button
ref="cancelButton"

View File

@ -7,6 +7,10 @@ fragment IssueNode on Issue {
referencePath: reference(full: true)
dueDate
timeEstimate
totalTimeSpent
humanTimeEstimate
humanTotalTimeSpent
emailsDisabled
confidential
webUrl
subscribed

View File

@ -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;
},

View File

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

View File

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

View File

@ -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);
},

View File

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

View File

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

View File

@ -0,0 +1,3 @@
import initPackageList from '~/packages/list/packages_list_app_bundle';
initPackageList();

View File

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

View File

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

View File

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

View File

@ -36,7 +36,7 @@
}
.file-title {
@extend .monospace;
@include gl-font-monospace;
line-height: 35px;
padding-top: 7px;
padding-bottom: 7px;

View File

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

View File

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

View File

@ -633,7 +633,7 @@
}
.btn-link:hover {
@extend a:hover;
color: $blue-800;
text-decoration: none;
}

View File

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

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Projects
module Packages
class InfrastructureRegistryController < Projects::ApplicationController
feature_category :infrastructure_as_code
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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') } }

View File

@ -0,0 +1,5 @@
---
title: Allow user to filter epics by their reaction emoji via GraphQL
merge_request: 58211
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Group SAML - Check SSO status on Git activity
merge_request: 56867
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Externalise strings in /application_settings/_realtime.html.haml
merge_request: 58039
author: nuwe1
type: other

View File

@ -0,0 +1,5 @@
---
title: Externalise strings in /application_settings/_repository_check.html.haml
merge_request: 58058
author: nuwe1
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix Rails/SaveBang Rubocop offenses for legacy github import
merge_request: 58054
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add composite index to support epic filtering by award emoji
merge_request: 57759
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Minor performance improvement for ref finder
merge_request: 58099
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Rename Submit issue to Create issue in boards and docs
merge_request: 58243
author: Yogi (@yo)
type: changed

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
4fa88193ae328f04465980210d9a43ce8cad978c157bda5e8ae9951538209268

View File

@ -0,0 +1 @@
d0f5341d76183882b68583bc012154566e99050c24a90c9b895d6863ad8f3273

View File

@ -0,0 +1 @@
d8a17ce963801559292265dd0a997d8dbc69d2fa8b8840622490f878bf1eaa6a

View File

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

View File

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

View File

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

View File

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

View File

@ -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**.
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,8 @@ class GitalyTestBuild
include GitalyTest
def run
set_bundler_config
abort 'gitaly build failed' unless build_gitaly
ensure_gitlab_shell_secret!

View File

@ -9,6 +9,7 @@ class GitalyTestSpawn
include GitalyTest
def run
set_bundler_config
install_gitaly_gems if ENV['CI']
check_gitaly_config!

View File

@ -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,14 +55,21 @@ module GitalyTest
'GITALY_TESTING_NO_GIT_HOOKS' => "1"
}
if ENV['CI']
bundle_path = File.expand_path('../vendor/gitaly-ruby', __dir__)
env_hash['BUNDLE_FLAGS'] += " --path=#{bundle_path}"
end
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__)
system('bundle', 'config', 'set', '--local', 'path', bundle_path, chdir: gemfile_dir)
end
end
# rubocop:enable GitlabSecurity/SystemCommandInjection
def config_path(service)
case service
when :gitaly

View File

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

View File

@ -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)
it_behaves_like 'issue boards sidebar'
expect(page).to have_selector('.issue-boards-sidebar')
def first_card
find('.board:nth-child(1)').first("[data-testid='board_card']")
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')
end
it 'closes sidebar when clicking close button' do
click_card(card)
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')
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

@ -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,
},
});
};

View File

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

View File

@ -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,
});
};

View File

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

View File

@ -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);
});

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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