Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-12-11 21:10:13 +00:00
parent 3172281335
commit 87911fabb2
43 changed files with 406 additions and 141 deletions

View file

@ -192,7 +192,7 @@ export default {
async createBoard() {
// TODO: change this to use `createBoard` mutation https://gitlab.com/gitlab-org/gitlab/-/issues/292466 is resolved
const boardData = await getBoardsPath(this.endpoints.boardsEndpoint, this.boardPayload);
await this.callBoardMutation(fullBoardId(boardData.data.id));
this.callBoardMutation(fullBoardId(boardData.data.id));
return boardData.data || boardData;
},

View file

@ -286,6 +286,7 @@ export default {
handleFilterSubmit() {
const filterTokens = uniqueTokens(this.filterValue);
this.filterValue = filterTokens;
if (this.recentSearchesStorageKey) {
this.recentSearchesPromise
.then(() => {
@ -302,6 +303,17 @@ export default {
this.blurSearchInput();
this.$emit('onFilter', this.removeQuotesEnclosure(filterTokens));
},
historyTokenOptionTitle(historyToken) {
const tokenOption = this.tokens
.find(token => token.type === historyToken.type)
?.options?.find(option => option.value === historyToken.value.data);
if (!tokenOption?.title) {
return historyToken.value.data;
}
return tokenOption.title;
},
},
};
</script>
@ -333,7 +345,7 @@ export default {
<span v-if="tokenTitles[token.type]"
>{{ tokenTitles[token.type] }} :{{ token.value.operator }}</span
>
<strong>{{ tokenSymbols[token.type] }}{{ token.value.data }}</strong>
<strong>{{ tokenSymbols[token.type] }}{{ historyTokenOptionTitle(token) }}</strong>
</span>
</template>
</template>

View file

@ -276,7 +276,7 @@
@include fade(left, $gray-light);
right: -5px;
.fa {
svg {
right: -7px;
}
}
@ -286,7 +286,7 @@
left: -5px;
text-align: center;
.fa {
svg {
left: -7px;
}
}
@ -337,7 +337,7 @@
@include fade(left, $white);
right: -5px;
.fa {
svg {
right: -7px;
}
}
@ -346,7 +346,7 @@
@include fade(right, $white);
left: -5px;
.fa {
svg {
left: -7px;
}
}

View file

@ -43,11 +43,6 @@
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
.fa {
color: $gl-text-color-secondary;
font-size: 14px;
}
}
.btn-group.open .btn-default {

View file

@ -30,12 +30,6 @@
margin-bottom: 0;
}
.member-controls {
.fa {
line-height: inherit;
}
}
&.existing-title {
@include media-breakpoint-up(sm) {
float: left;

View file

@ -163,12 +163,6 @@ $mr-widget-min-height: 69px;
.btn {
font-size: $gl-font-size;
&.dropdown-toggle {
.fa {
color: inherit;
}
}
}
.accept-merge-holder {
@ -341,13 +335,6 @@ $mr-widget-min-height: 69px;
}
}
.dropdown-toggle {
.fa {
margin-left: 0;
color: inherit;
}
}
.has-custom-error {
display: inline-block;
}
@ -552,10 +539,6 @@ $mr-widget-min-height: 69px;
align-items: center;
}
.dropdown-toggle .fa {
color: $gl-text-color;
}
.git-merge-container {
justify-content: space-between;
flex: 1;

View file

@ -778,13 +778,6 @@ $note-form-margin-left: 72px;
outline: none;
color: $blue-600;
}
.fa {
margin-right: 3px;
font-size: 10px;
line-height: 18px;
vertical-align: top;
}
}
.note-role {

View file

@ -35,8 +35,4 @@
.notification {
position: relative;
top: 1px;
.fa {
font-size: 18px;
}
}

View file

@ -140,10 +140,6 @@
margin-left: 0;
}
.fa {
color: $layout-link-gray;
}
svg {
fill: $layout-link-gray;
}
@ -163,13 +159,6 @@
height: 24px;
}
.dropdown-toggle,
.clone-dropdown-btn {
.fa {
color: unset;
}
}
.home-panel-action-button,
.project-action-button {
margin: $gl-padding $gl-padding-8 0 0;
@ -492,7 +481,7 @@
top: 0;
height: calc(100% - #{$browser-scrollbar-size});
.fa {
svg {
top: 50%;
margin-top: -$gl-padding-8;
}

View file

@ -188,7 +188,7 @@ class Projects::PipelinesController < Projects::ApplicationController
@charts = {}
@counts = {}
return unless Feature.enabled?(:graphql_pipeline_analytics)
return if Feature.enabled?(:graphql_pipeline_analytics)
@charts[:week] = Gitlab::Ci::Charts::WeekChart.new(project)
@charts[:month] = Gitlab::Ci::Charts::MonthChart.new(project)

View file

@ -9,8 +9,8 @@ module Resolvers
def resolve
authorize!(object)
BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_discussions_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Issue', 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id)
BatchLoader::GraphQL.for(object.id).batch do |ids, loader, args|
counts = Note.count_for_collection(ids, object.class.name, 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
@ -19,7 +19,8 @@ module Resolvers
end
def authorized_resource?(object)
context[:current_user].present? && Ability.allowed?(context[:current_user], :read_issue, object)
ability = "read_#{object.class.name.underscore}".to_sym
context[:current_user].present? && Ability.allowed?(context[:current_user], ability, object)
end
end
end

View file

@ -9,8 +9,8 @@ module Resolvers
def resolve
authorize!(object)
BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_notes_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Issue').index_by(&:noteable_id)
BatchLoader::GraphQL.for(object.id).batch(key: :user_notes_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, object.class.name).index_by(&:noteable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
@ -19,7 +19,8 @@ module Resolvers
end
def authorized_resource?(object)
context[:current_user].present? && Ability.allowed?(context[:current_user], :read_issue, object)
ability = "read_#{object.class.name.underscore}".to_sym
context[:current_user].present? && Ability.allowed?(context[:current_user], ability, object)
end
end
end

View file

@ -69,9 +69,11 @@ module Types
field :merge_commit_sha, GraphQL::STRING_TYPE, null: true,
description: 'SHA of the merge request commit (set once merged)'
field :user_notes_count, GraphQL::INT_TYPE, null: true,
description: 'User notes count of the merge request'
description: 'User notes count of the merge request',
resolver: Resolvers::UserNotesCountResolver
field :user_discussions_count, GraphQL::INT_TYPE, null: true,
description: 'Number of user discussions in the merge request'
description: 'Number of user discussions in the merge request',
resolver: Resolvers::UserDiscussionsCountResolver
field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true,
description: 'Indicates if the source branch of the merge request will be deleted after merge'
field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true,

View file

@ -102,7 +102,7 @@ module ServicesHelper
cancel_path: scoped_integrations_path,
can_test: integration.can_test?.to_s,
test_path: scoped_test_integration_path(integration),
reset_path: reset_integrations?(group: group) ? scoped_reset_integration_path(integration, group: group) : ''
reset_path: reset_integration?(integration, group: group) ? scoped_reset_integration_path(integration, group: group) : ''
}
end
@ -126,8 +126,8 @@ module ServicesHelper
!Gitlab.com?
end
def reset_integrations?(group: nil)
Feature.enabled?(:reset_integrations, group, type: :development)
def reset_integration?(integration, group: nil)
integration.persisted? && Feature.enabled?(:reset_integrations, group, type: :development)
end
extend self

View file

@ -86,9 +86,9 @@ class CommitCollection
# Batch load full Commits from the repository
# and map to a Hash of id => Commit
replacements = Hash[unenriched.map do |c|
[c.id, Commit.lazy(container, c.id)]
end.compact]
replacements = unenriched.each_with_object({}) do |c, result|
result[c.id] = Commit.lazy(container, c.id)
end.compact
# Replace the commits, keeping the same order
@commits = @commits.map do |original_commit|

View file

@ -28,7 +28,7 @@ class Packages::Package < ApplicationRecord
validates :project, presence: true
validates :name, presence: true
validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: -> { conan? || generic? }
validates :name, format: { with: Gitlab::Regex.package_name_regex }, unless: -> { conan? || generic? || debian? }
validates :name,
uniqueness: { scope: %i[project_id version package_type] }, unless: :conan?
@ -40,6 +40,8 @@ class Packages::Package < ApplicationRecord
validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :name, format: { with: Gitlab::Regex.generic_package_name_regex }, if: :generic?
validates :name, format: { with: Gitlab::Regex.nuget_package_name_regex }, if: :nuget?
validates :name, format: { with: Gitlab::Regex.debian_package_name_regex }, if: :debian_package?
validates :name, inclusion: { in: %w[incoming] }, if: :debian_incoming?
validates :version, format: { with: Gitlab::Regex.nuget_version_regex }, if: :nuget?
validates :version, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? }
@ -51,6 +53,11 @@ class Packages::Package < ApplicationRecord
presence: true,
format: { with: Gitlab::Regex.generic_package_version_regex },
if: :generic?
validates :version,
presence: true,
format: { with: Gitlab::Regex.debian_version_regex },
if: :debian_package?
validate :forbidden_debian_changes, if: :debian?
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6, generic: 7, golang: 8, debian: 9 }
@ -184,6 +191,14 @@ class Packages::Package < ApplicationRecord
tags.pluck(:name)
end
def debian_incoming?
debian? && version.nil?
end
def debian_package?
debian? && !version.nil?
end
private
def composer_tag_version?
@ -228,4 +243,13 @@ class Packages::Package < ApplicationRecord
errors.add(:base, _('Package already exists'))
end
end
def forbidden_debian_changes
return unless persisted?
# Debian incoming
if version_was.nil? || version.nil?
errors.add(:version, _('cannot be changed')) if version_changed?
end
end
end

View file

@ -0,0 +1,5 @@
---
title: Track index bloat estimate
merge_request: 49822
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Capture subgroup creation failure during Group Import via archive file
merge_request: 49484
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add ldap encrypted credentials to the usage data
merge_request: 48210
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Reduce object allocations for large merge request
merge_request: 49563
author:
type: performance

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddBloatEstimateToReindexAction < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :postgres_reindex_actions, :bloat_estimate_bytes_start, :bigint
end
end

View file

@ -0,0 +1 @@
ecf6b392f35bb0ef905144a4605bcb927ce767240e47ec3b0653a94139b987bd

View file

@ -15216,6 +15216,7 @@ CREATE TABLE postgres_reindex_actions (
ondisk_size_bytes_end bigint,
state smallint DEFAULT 0 NOT NULL,
index_identifier text NOT NULL,
bloat_estimate_bytes_start bigint,
CONSTRAINT check_f12527622c CHECK ((char_length(index_identifier) <= 255))
);

View file

@ -8,11 +8,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36001) in GitLab 13.2.
Instance-level Kubernetes clusters allow you to connect a Kubernetes cluster to the GitLab instance, which enables you to use the same cluster across multiple projects. [More information](../user/instance/clusters/index.md)
NOTE:
Users need admin access to use these endpoints.
Use these API endpoints with your instance clusters, which enable you to use the same cluster across multiple projects. [More information](../user/instance/clusters/index.md)
## List instance clusters
Returns a list of instance clusters.

View file

@ -18,7 +18,8 @@ module Gitlab
action = create!(
index_identifier: index.identifier,
action_start: Time.zone.now,
ondisk_size_bytes_start: index.ondisk_size_bytes
ondisk_size_bytes_start: index.ondisk_size_bytes,
bloat_estimate_bytes_start: index.bloat_size
)
yield

View file

@ -74,6 +74,12 @@ module Gitlab
group = create_group(group_attributes)
restore_group(group, group_attributes)
rescue => e
import_failure_service.log_import_failure(
source: 'process_child',
relation_key: 'group',
exception: e
)
end
def create_group(group_attributes)
@ -83,13 +89,17 @@ module Gitlab
parent_group = @groups_mapping.fetch(parent_id) { raise(ArgumentError, 'Parent group not found') }
::Groups::CreateService.new(
group = ::Groups::CreateService.new(
user,
name: name,
path: path,
parent_id: parent_group.id,
visibility_level: sub_group_visibility_level(group_attributes.attributes, parent_group)
).execute
group.validate!
group
end
def restore_group(group, group_attributes)
@ -134,6 +144,10 @@ module Gitlab
)
end
end
def import_failure_service
Gitlab::ImportExport::ImportFailureService.new(@top_level_group)
end
end
end
end

View file

@ -236,7 +236,9 @@ module Gitlab
def system_usage_data_settings
{
settings: {}
settings: {
ldap_encrypted_secrets_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth::Ldap::Config.encrypted_secrets.active? }
}
}
end

View file

@ -28551,6 +28551,9 @@ msgstr ""
msgid "ThreatMonitoring|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
msgid "ThreatMonitoring|There was an error while updating the status of the alert. Please try again."
msgstr ""
msgid "ThreatMonitoring|Threat Monitoring"
msgstr ""
@ -32244,6 +32247,9 @@ msgstr ""
msgid "cannot be a date in the past"
msgstr ""
msgid "cannot be changed"
msgstr ""
msgid "cannot be changed if a personal project has container registry tags."
msgstr ""

View file

@ -22,7 +22,14 @@ FactoryBot.define do
end
factory :debian_package do
sequence(:name) { |n| "package-#{n}" }
sequence(:version) { |n| "1.0-#{n}" }
package_type { :debian }
factory :debian_incoming do
name { 'incoming' }
version { nil }
end
end
factory :npm_package do

View file

@ -0,0 +1 @@
{"name":"ymg09t5704clnxnqfgaj2h098gz4r7gyx4wc3fzmlqj1en24zf","path":"ymg09t5704clnxnqfgaj2h098gz4r7gyx4wc3fzmlqj1en24zf","owner_id":123,"created_at":"2019-11-20 17:01:53 UTC","updated_at":"2019-11-20 17:05:44 UTC","description":"Group Description","avatar":{"url":null},"membership_lock":false,"share_with_group_lock":false,"visibility_level":0,"request_access_enabled":true,"ldap_sync_status":"ready","ldap_sync_error":null,"ldap_sync_last_update_at":null,"ldap_sync_last_successful_update_at":null,"ldap_sync_last_sync_at":null,"lfs_enabled":null,"parent_id":null,"shared_runners_minutes_limit":null,"repository_size_limit":null,"require_two_factor_authentication":false,"two_factor_grace_period":48,"plan_id":null,"project_creation_level":2,"trial_ends_on":null,"file_template_project_id":null,"saml_discovery_token":"rBKx3ioz","custom_project_templates_group_id":null,"auto_devops_enabled":null,"extra_shared_runners_minutes_limit":null,"last_ci_minutes_notification_at":null,"last_ci_minutes_usage_notification_level":null,"runners_token":"token","runners_token_encrypted":"encrypted","subgroup_creation_level":1,"emails_disabled":null,"max_pages_size":null,"max_artifacts_size":null,"id":4351}

View file

@ -0,0 +1 @@
{"name":"a","path":"a","owner_id":null,"created_at":"2019-11-20 17:01:53 UTC","updated_at":"2019-11-20 17:05:44 UTC","description":"","avatar":{"url":null},"membership_lock":false,"share_with_group_lock":false,"visibility_level":0,"request_access_enabled":true,"ldap_sync_status":"ready","ldap_sync_error":null,"ldap_sync_last_update_at":null,"ldap_sync_last_successful_update_at":null,"ldap_sync_last_sync_at":null,"lfs_enabled":null,"parent_id":4351,"shared_runners_minutes_limit":null,"repository_size_limit":null,"require_two_factor_authentication":false,"two_factor_grace_period":48,"plan_id":null,"project_creation_level":2,"trial_ends_on":null,"file_template_project_id":null,"saml_discovery_token":"ki3Xnjw3","custom_project_templates_group_id":null,"auto_devops_enabled":null,"extra_shared_runners_minutes_limit":null,"last_ci_minutes_notification_at":null,"last_ci_minutes_usage_notification_level":null,"subgroup_creation_level":1,"emails_disabled":null,"max_pages_size":null,"max_artifacts_size":null,"id":4352}

View file

@ -4,7 +4,6 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'jest/helpers/test_constants';
import { GlModal } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import BoardScope from 'ee_component/boards/components/board_scope.vue';
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
@ -64,7 +63,6 @@ describe('BoardForm', () => {
const findFormWrapper = () => wrapper.find('[data-testid="board-form-wrapper"]');
const findDeleteConfirmation = () => wrapper.find('[data-testid="delete-confirmation-message"]');
const findConfigurationOptions = () => wrapper.find(BoardConfigurationOptions);
const findBoardScope = () => wrapper.find(BoardScope);
const findInput = () => wrapper.find('#board-new-name');
const createComponent = (props, data) => {
@ -172,12 +170,6 @@ describe('BoardForm', () => {
});
});
it('passes a correct collapseScope property to BoardScope component on scoped board', async () => {
createComponent({ canAdminBoard: true, scopedIssueBoardFeatureEnabled: true });
await waitForPromises();
expect(findBoardScope().props('collapseScope')).toBe(true);
});
describe('when submitting a create event', () => {
beforeEach(() => {
const url = `${endpoints.boardsEndpoint}.json`;
@ -255,12 +247,6 @@ describe('BoardForm', () => {
});
});
it('passes a correct collapseScope property to BoardScope component on scoped board', async () => {
createComponent({ canAdminBoard: true, scopedIssueBoardFeatureEnabled: true });
await waitForPromises();
expect(findBoardScope().props('collapseScope')).toBe(false);
});
describe('when submitting an update event', () => {
beforeEach(() => {
const url = endpoints.boardsEndpoint;

View file

@ -17,11 +17,14 @@ import RecentSearchesService from '~/filtered_search/services/recent_searches_se
import {
mockAvailableTokens,
mockMembershipToken,
mockMembershipTokenOptionsWithoutTitles,
mockSortOptions,
mockHistoryItems,
tokenValueAuthor,
tokenValueLabel,
tokenValueMilestone,
tokenValueMembership,
} from './mock_data';
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils', () => ({
@ -412,6 +415,42 @@ describe('FilteredSearchBarRoot', () => {
wrapperFullMount.destroy();
});
describe('when token options have `title` attribute defined', () => {
it('renders search history items using the provided `title` attribute', async () => {
const wrapperFullMount = createComponent({
sortOptions: mockSortOptions,
tokens: [mockMembershipToken],
shallow: false,
});
wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
await wrapperFullMount.vm.$nextTick();
expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := Direct');
wrapperFullMount.destroy();
});
});
describe('when token options have do not have `title` attribute defined', () => {
it('renders search history items using the provided `value` attribute', async () => {
const wrapperFullMount = createComponent({
sortOptions: mockSortOptions,
tokens: [mockMembershipTokenOptionsWithoutTitles],
shallow: false,
});
wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
await wrapperFullMount.vm.$nextTick();
expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := exclude');
wrapperFullMount.destroy();
});
});
it('renders sort dropdown component', () => {
expect(wrapper.find(GlButtonGroup).exists()).toBe(true);
expect(wrapper.find(GlDropdown).exists()).toBe(true);

View file

@ -1,3 +1,4 @@
import { GlFilteredSearchToken } from '@gitlab/ui';
import { mockLabels } from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
import Api from '~/api';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
@ -102,6 +103,21 @@ export const mockMilestoneToken = {
fetchMilestones: () => Promise.resolve({ data: mockMilestones }),
};
export const mockMembershipToken = {
type: 'with_inherited_permissions',
icon: 'group',
title: 'Membership',
token: GlFilteredSearchToken,
unique: true,
operators: [{ value: '=', description: 'is' }],
options: [{ value: 'exclude', title: 'Direct' }, { value: 'only', title: 'Inherited' }],
};
export const mockMembershipTokenOptionsWithoutTitles = {
...mockMembershipToken,
options: [{ value: 'exclude' }, { value: 'only' }],
};
export const mockAvailableTokens = [mockAuthorToken, mockLabelToken, mockMilestoneToken];
export const tokenValueAuthor = {
@ -128,6 +144,14 @@ export const tokenValueMilestone = {
},
};
export const tokenValueMembership = {
type: 'with_inherited_permissions',
value: {
operator: '=',
data: 'exclude',
},
};
export const tokenValuePlain = {
type: 'filtered-search-term',
value: { data: 'foo' },

View file

@ -7,43 +7,82 @@ RSpec.describe Resolvers::UserNotesCountResolver do
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:private_issue) { create(:issue, project: private_project) }
let_it_be(:public_notes) { create_list(:note, 2, noteable: issue, project: project) }
let_it_be(:system_note) { create(:note, system: true, noteable: issue, project: project) }
let_it_be(:private_notes) { create_list(:note, 3, noteable: private_issue, project: private_project) }
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:private_project) { create(:project, :repository, :private) }
specify do
expect(described_class).to have_nullable_graphql_type(GraphQL::INT_TYPE)
end
context 'when counting notes from a public issue' do
subject { batch_sync { resolve_user_notes_count(issue) } }
context 'when counting notes from an issue' do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:private_issue) { create(:issue, project: private_project) }
let_it_be(:public_notes) { create_list(:note, 2, noteable: issue, project: project) }
let_it_be(:system_note) { create(:note, system: true, noteable: issue, project: project) }
let_it_be(:private_notes) { create_list(:note, 3, noteable: private_issue, project: private_project) }
it 'returns the number of non-system notes for the issue' do
expect(subject).to eq(2)
context 'when counting notes from a public issue' do
subject { batch_sync { resolve_user_notes_count(issue) } }
it 'returns the number of non-system notes for the issue' do
expect(subject).to eq(2)
end
end
context 'when a user has permission to view notes' do
before do
private_project.add_developer(user)
end
subject { batch_sync { resolve_user_notes_count(private_issue) } }
it 'returns the number of notes for the issue' do
expect(subject).to eq(3)
end
end
context 'when a user does not have permission to view discussions' do
subject { batch_sync { resolve_user_notes_count(private_issue) } }
it 'returns no notes' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
context 'when a user has permission to view notes' do
before do
private_project.add_developer(user)
context 'when counting notes from a merge request' do
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let_it_be(:private_merge_request) { create(:merge_request, source_project: private_project) }
let_it_be(:public_notes) { create_list(:note, 2, noteable: merge_request, project: project) }
let_it_be(:system_note) { create(:note, system: true, noteable: merge_request, project: project) }
let_it_be(:private_notes) { create_list(:note, 3, noteable: private_merge_request, project: private_project) }
context 'when counting notes from a public merge request' do
subject { batch_sync { resolve_user_notes_count(merge_request) } }
it 'returns the number of non-system notes for the merge request' do
expect(subject).to eq(2)
end
end
subject { batch_sync { resolve_user_notes_count(private_issue) } }
context 'when a user has permission to view notes' do
before do
private_project.add_developer(user)
end
it 'returns the number of notes for the issue' do
expect(subject).to eq(3)
subject { batch_sync { resolve_user_notes_count(private_merge_request) } }
it 'returns the number of notes for the merge request' do
expect(subject).to eq(3)
end
end
end
context 'when a user does not have permission to view discussions' do
subject { batch_sync { resolve_user_notes_count(private_issue) } }
context 'when a user does not have permission to view discussions' do
subject { batch_sync { resolve_user_notes_count(private_merge_request) } }
it 'returns no notes' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
it 'returns no notes' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end

View file

@ -49,34 +49,50 @@ RSpec.describe ServicesHelper do
end
end
describe '#reset_integrations?' do
describe '#reset_integration?' do
let(:group) { nil }
subject { helper.reset_integrations?(group: group) }
subject { helper.reset_integration?(integration, group: group) }
context 'when `reset_integrations` is not enabled' do
it 'returns false' do
stub_feature_flags(reset_integrations: false)
context 'when integration is existing record' do
let_it_be(:integration) { create(:jira_service) }
is_expected.to eq(false)
context 'when `reset_integrations` is not enabled' do
it 'returns false' do
stub_feature_flags(reset_integrations: false)
is_expected.to eq(false)
end
end
context 'when `reset_integrations` is enabled' do
it 'returns true' do
stub_feature_flags(reset_integrations: true)
is_expected.to eq(true)
end
end
context 'when `reset_integrations` is enabled for a group' do
let(:group) { build_stubbed(:group) }
it 'returns true' do
stub_feature_flags(reset_integrations: group)
is_expected.to eq(true)
end
end
end
context 'when `reset_integrations` is enabled' do
it 'returns true' do
stub_feature_flags(reset_integrations: true)
context 'when integration is a new record' do
let_it_be(:integration) { build(:jira_service) }
is_expected.to eq(true)
end
end
context 'when `reset_integrations` is enabled' do
it 'returns false' do
stub_feature_flags(reset_integrations: true)
context 'when `reset_integrations` is enabled for a group' do
let(:group) { build_stubbed(:group) }
it 'returns true' do
stub_feature_flags(reset_integrations: group)
is_expected.to eq(true)
is_expected.to eq(false)
end
end
end
end

View file

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Reindexing::ReindexAction, '.keep_track_of' do
let(:index) { double('index', identifier: 'public.something', ondisk_size_bytes: 10240, reload: nil) }
let(:index) { double('index', identifier: 'public.something', ondisk_size_bytes: 10240, reload: nil, bloat_size: 42) }
let(:size_after) { 512 }
it 'yields to the caller' do
@ -47,6 +47,12 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexAction, '.keep_track_of' do
expect(find_record.ondisk_size_bytes_end).to eq(size_after)
end
it 'creates the record with the indexes bloat estimate' do
described_class.keep_track_of(index) do
expect(find_record.bloat_estimate_bytes_start).to eq(index.bloat_size)
end
end
context 'in case of errors' do
it 'sets the state to failed' do
expect do

View file

@ -75,12 +75,31 @@ RSpec.describe Gitlab::ImportExport::Group::TreeRestorer do
before do
setup_import_export_config('group_exports/child_with_no_parent')
expect(group_tree_restorer.restore).to be_falsey
end
it 'fails when a child group does not have a valid parent_id' do
expect(shared.errors).to include('Parent group not found')
it 'captures import failures when a child group does not have a valid parent_id' do
group_tree_restorer.restore
expect(group.import_failures.first.exception_message).to eq('Parent group not found')
end
end
context 'when child group creation fails' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:shared) { Gitlab::ImportExport::Shared.new(group) }
let(:group_tree_restorer) { described_class.new(user: user, shared: shared, group: group) }
before do
setup_import_export_config('group_exports/child_short_name')
end
it 'captures import failure' do
exception_message = 'Validation failed: Group URL is too short (minimum is 2 characters)'
group_tree_restorer.restore
expect(group.import_failures.first.exception_message).to eq(exception_message)
end
end

View file

@ -1038,6 +1038,14 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
end
describe ".system_usage_data_settings" do
subject { described_class.system_usage_data_settings }
it 'gathers settings usage data', :aggregate_failures do
expect(subject[:settings][:ldap_encrypted_secrets_enabled]).to eq(Gitlab::Auth::Ldap::Config.encrypted_secrets.active?)
end
end
end
describe '.merge_requests_users', :clean_gitlab_redis_shared_state do

View file

@ -111,6 +111,24 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('%foo%bar').for(:name) }
end
context 'debian package' do
subject { build(:debian_package) }
it { is_expected.to allow_value('0ad').for(:name) }
it { is_expected.to allow_value('g++').for(:name) }
it { is_expected.not_to allow_value('a_b').for(:name) }
end
context 'debian incoming' do
subject { create(:debian_incoming) }
# Only 'incoming' is accepted
it { is_expected.to allow_value('incoming').for(:name) }
it { is_expected.not_to allow_value('0ad').for(:name) }
it { is_expected.not_to allow_value('g++').for(:name) }
it { is_expected.not_to allow_value('a_b').for(:name) }
end
context 'generic package' do
subject { build_stubbed(:generic_package) }
@ -180,6 +198,21 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to allow_value('2.x-dev').for(:version) }
end
context 'debian package' do
subject { build(:debian_package) }
it { is_expected.to allow_value('2:4.9.5+dfsg-5+deb10u1').for(:version) }
it { is_expected.not_to allow_value('1_0').for(:version) }
end
context 'debian incoming' do
subject { create(:debian_incoming) }
it { is_expected.to allow_value(nil).for(:version) }
it { is_expected.not_to allow_value('2:4.9.5+dfsg-5+deb10u1').for(:version) }
it { is_expected.not_to allow_value('1_0').for(:version) }
end
context 'maven package' do
subject { build_stubbed(:maven_package) }
@ -621,6 +654,46 @@ RSpec.describe Packages::Package, type: :model do
end
end
describe '#debian_incoming?' do
let(:package) { build(:package) }
subject { package.debian_incoming? }
it { is_expected.to eq(false) }
context 'with debian_incoming' do
let(:package) { create(:debian_incoming) }
it { is_expected.to eq(true) }
end
context 'with debian_package' do
let(:package) { create(:debian_package) }
it { is_expected.to eq(false) }
end
end
describe '#debian_package?' do
let(:package) { build(:package) }
subject { package.debian_package? }
it { is_expected.to eq(false) }
context 'with debian_incoming' do
let(:package) { create(:debian_incoming) }
it { is_expected.to eq(false) }
end
context 'with debian_package' do
let(:package) { create(:debian_package) }
it { is_expected.to eq(true) }
end
end
describe 'plan_limits' do
Packages::Package.package_types.keys.without('composer').each do |pt|
plan_limit_name = if pt == 'generic'

View file

@ -4,6 +4,6 @@
source 'https://rubygems.org'
gem 'overcommit'
gem 'gitlab-styles', '~> 5.2.0', require: false
gem 'gitlab-styles', '~> 5.3.0', require: false
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false

View file

@ -11,7 +11,7 @@ GEM
childprocess (3.0.0)
concurrent-ruby (1.1.7)
ffi (1.12.2)
gitlab-styles (5.2.0)
gitlab-styles (5.3.0)
rubocop (~> 0.89.1)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.8.1)
@ -88,7 +88,7 @@ PLATFORMS
ruby
DEPENDENCIES
gitlab-styles (~> 5.2.0)
gitlab-styles (~> 5.3.0)
haml_lint (~> 0.34.0)
overcommit
scss_lint (~> 0.56.0)