Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-07-23 03:24:50 +00:00
parent 8212b8fd70
commit 1c1b987177
24 changed files with 514 additions and 30 deletions

View File

@ -476,7 +476,7 @@ group :development, :test do
gem 'awesome_print', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'database_cleaner-active_record', '~> 2.1.0', feature_category: :database
gem 'database_cleaner-active_record', '~> 2.2.0', feature_category: :database
gem 'rspec-rails', '~> 6.1.1', feature_category: :shared
gem 'factory_bot_rails', '~> 6.4.3', feature_category: :tooling
@ -515,7 +515,7 @@ group :development, :test do
# For now we only use vite in development / test, and not for production builds
# See: https://gitlab.com/gitlab-org/frontend/rfcs/-/issues/106
gem 'vite_rails', '~> 3.0.17', feature_category: :shared
gem 'vite_ruby', '~> 3.5.0', feature_category: :shared
gem 'vite_ruby', '~> 3.7.0', feature_category: :shared
gem 'gitlab-housekeeper', path: 'gems/gitlab-housekeeper', feature_category: :tooling
end
@ -569,7 +569,7 @@ group :test do
# Moved in `test` because https://gitlab.com/gitlab-org/gitlab/-/issues/217527
gem 'derailed_benchmarks', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'gitlab_quality-test_tooling', '~> 1.31.0', require: false, feature_category: :tooling
gem 'gitlab_quality-test_tooling', '~> 1.32.0', require: false, feature_category: :tooling
end
gem 'octokit', '~> 9.0', feature_category: :importers

View File

@ -101,7 +101,7 @@
{"name":"cvss-suite","version":"3.0.1","platform":"ruby","checksum":"b5ca9e9e94032a42fd0dc28c1e305378b62c949e35ed7111fc4a1d76f68ad3f9"},
{"name":"danger","version":"9.4.2","platform":"ruby","checksum":"43e552c6731030235a30fdeafe703d2e2ab9c30917154489cb0ecd9ad3259d80"},
{"name":"danger-gitlab","version":"8.0.0","platform":"ruby","checksum":"497dd7d0f6513913de651019223d8058cf494df10acbd17de92b175dfa04a3a8"},
{"name":"database_cleaner-active_record","version":"2.1.0","platform":"ruby","checksum":"7384b973d67bcc1b5a850b876a4638aa83cca3bc88f9d87562fe25cd2dd60d8a"},
{"name":"database_cleaner-active_record","version":"2.2.0","platform":"ruby","checksum":"3228d6d8ec1f2103fd6ab468dae923424318bcfabcf5dd5b02e5fcb0c486e1c7"},
{"name":"database_cleaner-core","version":"2.0.1","platform":"ruby","checksum":"8646574c32162e59ed7b5258a97a208d3c44551b854e510994f24683865d846c"},
{"name":"date","version":"3.3.3","platform":"java","checksum":"584e0a582d1eb2207b4eaac089d8a43f2ca10bea02682f286099642f15c56cce"},
{"name":"date","version":"3.3.3","platform":"ruby","checksum":"819792019d5712b748fb15f6dfaaedef14b0328723ef23583ea35f186774530f"},
@ -227,7 +227,7 @@
{"name":"gitlab-styles","version":"12.0.1","platform":"ruby","checksum":"d8a302b0ab0e1f18e2d11501760f1b85c5e70b5e5ca628828a0786c7984ed133"},
{"name":"gitlab_chronic_duration","version":"0.12.0","platform":"ruby","checksum":"0d766944d415b5c831f176871ee8625783fc0c5bfbef2d79a3a616f207ffc16d"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"gitlab_quality-test_tooling","version":"1.31.0","platform":"ruby","checksum":"c13d38f2ba01469179db7211008722b1f4a55270cca561d832b1eee438124f52"},
{"name":"gitlab_quality-test_tooling","version":"1.32.0","platform":"ruby","checksum":"72b7bb243d3e1f8006bef4bcf3585480a774f72bb9dd97bfcc52db4b32fd30fb"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},
@ -749,7 +749,7 @@
{"name":"view_component","version":"3.13.0","platform":"ruby","checksum":"316e6479f51387160e7eb372d33cbb97d586ac2ed9d9fe80495cec48b346187b"},
{"name":"virtus","version":"2.0.0","platform":"ruby","checksum":"8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2"},
{"name":"vite_rails","version":"3.0.17","platform":"ruby","checksum":"b90e85a3e55802981cbdb43a4101d944b1e7055bfe85599d9cb7de0f1ea58bcc"},
{"name":"vite_ruby","version":"3.5.0","platform":"ruby","checksum":"a3e5da3fdd816f831cb1530c4001a790aac862c89f74c09f48d5a3cfed3dea73"},
{"name":"vite_ruby","version":"3.7.0","platform":"ruby","checksum":"4a34ada95e66821390896cf518c772146befc8f23d8954fa04416a5537e5f739"},
{"name":"vmstat","version":"2.3.0","platform":"ruby","checksum":"ab5446a3e3bd0a9cdb9d9ac69a0bbd119c4f161d945a0846a519dd7018af656d"},
{"name":"warden","version":"1.2.9","platform":"ruby","checksum":"46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0"},
{"name":"warning","version":"1.3.0","platform":"ruby","checksum":"23695a5d8e50bd5c46068931b529bee0b28e4982cbcefbe77d867800dde8069e"},

View File

@ -455,7 +455,7 @@ GEM
danger-gitlab (8.0.0)
danger
gitlab (~> 4.2, >= 4.2.0)
database_cleaner-active_record (2.1.0)
database_cleaner-active_record (2.2.0)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
@ -748,7 +748,7 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
gitlab_quality-test_tooling (1.31.0)
gitlab_quality-test_tooling (1.32.0)
activesupport (>= 7.0, < 7.2)
amatch (~> 0.4.1)
gitlab (~> 4.19)
@ -1874,7 +1874,7 @@ GEM
vite_rails (3.0.17)
railties (>= 5.1, < 8)
vite_ruby (~> 3.0, >= 3.2.2)
vite_ruby (3.5.0)
vite_ruby (3.7.0)
dry-cli (>= 0.7, < 2)
rack-proxy (~> 0.6, >= 0.6.1)
zeitwerk (~> 2.2)
@ -1973,7 +1973,7 @@ DEPENDENCIES
cssbundling-rails (= 1.4.0)
csv_builder!
cvss-suite (~> 3.0.1)
database_cleaner-active_record (~> 2.1.0)
database_cleaner-active_record (~> 2.2.0)
deckar01-task_list (= 2.3.4)
declarative_policy (~> 1.1.0)
deprecation_toolkit (~> 1.5.1)
@ -2043,7 +2043,7 @@ DEPENDENCIES
gitlab-utils!
gitlab_chronic_duration (~> 0.12)
gitlab_omniauth-ldap (~> 2.2.0)
gitlab_quality-test_tooling (~> 1.31.0)
gitlab_quality-test_tooling (~> 1.32.0)
gon (~> 6.4.0)
google-apis-androidpublisher_v3 (~> 0.34.0)
google-apis-cloudbilling_v1 (~> 0.21.0)
@ -2273,7 +2273,7 @@ DEPENDENCIES
version_sorter (~> 2.3)
view_component (~> 3.13.0)
vite_rails (~> 3.0.17)
vite_ruby (~> 3.5.0)
vite_ruby (~> 3.7.0)
vmstat (~> 2.3.0)
warning (~> 1.3.0)
webauthn (~> 3.0)

View File

@ -0,0 +1,22 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
fragment Group on Group {
id
name
fullName
fullPath
}
query getSubGroups($fullPath: ID!, $search: String, $after: String) {
group(fullPath: $fullPath) {
...Group
descendantGroups(search: $search, after: $after, first: 100) {
nodes {
...Group
}
pageInfo {
...PageInfo
}
}
}
}

View File

@ -18,6 +18,7 @@ import {
TOKEN_TYPE_CONTACT,
TOKEN_TYPE_DRAFT,
TOKEN_TYPE_EPIC,
TOKEN_TYPE_GROUP,
TOKEN_TYPE_HEALTH,
TOKEN_TYPE_ITERATION,
TOKEN_TYPE_LABEL,
@ -235,6 +236,16 @@ export const filtersMap = {
},
},
},
[TOKEN_TYPE_GROUP]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'fullPath',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'group_path',
},
},
},
[TOKEN_TYPE_REVIEWER]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'reviewerUsername',

View File

@ -0,0 +1,109 @@
<script>
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import { pick } from 'lodash';
import { createAlert } from '~/alert';
import searchGroupsQuery from '~/boards/graphql/sub_groups.query.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
export default {
separator: '::',
components: {
BaseToken,
GlFilteredSearchSuggestion,
},
props: {
config: {
type: Object,
required: true,
},
value: {
type: Object,
required: true,
},
active: {
type: Boolean,
required: true,
},
},
data() {
return {
groups: this.config.initialGroups || [],
loading: false,
};
},
computed: {
defaultGroups() {
return this.config.defaultGroups || [];
},
},
methods: {
fetchGroups(search = '') {
return this.$apollo
.query({
query: searchGroupsQuery,
variables: { fullPath: this.config.fullPath, search },
})
.then(({ data }) => data.group);
},
fetchGroupsBySearchTerm(search) {
this.loading = true;
this.fetchGroups(search)
.then((response) => {
const parentGroup = pick(response, ['id', 'name', 'fullName', 'fullPath']) || {};
this.groups = [parentGroup, ...(response?.descendantGroups?.nodes || [])];
})
.catch(() => createAlert({ message: __('There was a problem fetching groups.') }))
.finally(() => {
this.loading = false;
});
},
getActiveGroup(groups, data) {
if (data && groups.length) {
return groups.find((group) => this.getValue(group) === data);
}
return undefined;
},
getValue(group) {
return group.fullPath;
},
displayValue(group) {
return `${this.getGroupIdProperty(group)}${this.$options.separator}${group?.fullName}`;
},
getGroupIdProperty(group) {
return getIdFromGraphQLId(group.id);
},
},
};
</script>
<template>
<base-token
:config="config"
:value="value"
:active="active"
:suggestions-loading="loading"
:suggestions="groups"
:get-active-token-value="getActiveGroup"
:default-suggestions="defaultGroups"
search-by="title"
:value-identifier="getValue"
v-bind="$attrs"
@fetch-suggestions="fetchGroupsBySearchTerm"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
{{ activeTokenValue ? displayValue(activeTokenValue) : inputValue }}
</template>
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
v-for="group in suggestions"
:key="group.id"
:value="getValue(group)"
>
{{ group.fullName }}
</gl-filtered-search-suggestion>
</template>
</base-token>
</template>

View File

@ -12,6 +12,7 @@ import {
WORKSPACE_GROUP,
WORKSPACE_PROJECT,
} from '~/issues/constants';
import { AutocompleteCache } from '~/issues/dashboard/utils';
import { defaultTypeTokenOptions } from '~/issues/list/constants';
import searchLabelsQuery from '~/issues/list/queries/search_labels.query.graphql';
import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
@ -29,14 +30,20 @@ import {
OPERATORS_IS_NOT_OR,
TOKEN_TITLE_ASSIGNEE,
TOKEN_TITLE_AUTHOR,
TOKEN_TITLE_CONFIDENTIAL,
TOKEN_TITLE_GROUP,
TOKEN_TITLE_LABEL,
TOKEN_TITLE_MILESTONE,
TOKEN_TITLE_MY_REACTION,
TOKEN_TITLE_SEARCH_WITHIN,
TOKEN_TITLE_TYPE,
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_CONFIDENTIAL,
TOKEN_TYPE_GROUP,
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_SEARCH_WITHIN,
TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
@ -47,11 +54,15 @@ import { STATE_CLOSED } from '../../constants';
import { sortOptions, urlSortParams } from '../constants';
import getWorkItemsQuery from '../queries/get_work_items.query.graphql';
const UserToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/user_token.vue');
const EmojiToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue');
const GroupToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/group_token.vue');
const LabelToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue');
const MilestoneToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue');
const UserToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/user_token.vue');
export default {
issuableListTabs,
@ -63,7 +74,14 @@ export default {
IssueCardTimeInfo,
},
mixins: [glFeatureFlagMixin()],
inject: ['fullPath', 'initialSort', 'isGroup', 'isSignedIn', 'workItemType'],
inject: [
'autocompleteAwardEmojisPath',
'fullPath',
'initialSort',
'isGroup',
'isSignedIn',
'workItemType',
],
props: {
eeCreatedWorkItemsCount: {
type: Number,
@ -97,6 +115,7 @@ export default {
search: this.searchQuery,
...this.apiFilterParams,
...this.pageParams,
includeDescendants: !this.apiFilterParams.fullPath,
types: this.apiFilterParams.types || [this.workItemType],
};
},
@ -185,6 +204,15 @@ export default {
preloadedUsers,
multiSelect: this.glFeatures.groupMultiSelectTokens,
},
{
type: TOKEN_TYPE_GROUP,
icon: 'group',
title: TOKEN_TITLE_GROUP,
unique: true,
token: GroupToken,
operators: OPERATORS_IS,
fullPath: this.fullPath,
},
{
type: TOKEN_TYPE_LABEL,
title: TOKEN_TITLE_LABEL,
@ -231,6 +259,31 @@ export default {
});
}
if (this.isSignedIn) {
tokens.push({
type: TOKEN_TYPE_CONFIDENTIAL,
title: TOKEN_TITLE_CONFIDENTIAL,
icon: 'eye-slash',
token: GlFilteredSearchToken,
unique: true,
operators: OPERATORS_IS,
options: [
{ icon: 'eye-slash', value: 'yes', title: __('Yes') },
{ icon: 'eye', value: 'no', title: __('No') },
],
});
tokens.push({
type: TOKEN_TYPE_MY_REACTION,
title: TOKEN_TITLE_MY_REACTION,
icon: 'thumb-up',
token: EmojiToken,
unique: true,
fetchEmojis: this.fetchEmojis,
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-my_reaction`,
});
}
tokens.sort((a, b) => a.title.localeCompare(b.title));
return tokens;
@ -251,7 +304,18 @@ export default {
this.$apollo.queries.workItems.refetch();
},
},
created() {
this.autocompleteCache = new AutocompleteCache();
},
methods: {
fetchEmojis(search) {
return this.autocompleteCache.fetch({
url: this.autocompleteAwardEmojisPath,
cacheName: 'emojis',
searchProperty: 'name',
search,
});
},
fetchLabelsWithFetchPolicy(search, fetchPolicy = fetchPolicies.CACHE_FIRST) {
return this.$apollo
.query({

View File

@ -14,6 +14,7 @@ export const mountWorkItemsListApp = () => {
Vue.use(VueApollo);
const {
autocompleteAwardEmojisPath,
fullPath,
hasEpicsFeature,
hasIssuableHealthStatusFeature,
@ -34,6 +35,7 @@ export const mountWorkItemsListApp = () => {
name: 'WorkItemsListRoot',
apolloProvider,
provide: {
autocompleteAwardEmojisPath,
fullPath,
hasEpicsFeature: parseBoolean(hasEpicsFeature),
hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),

View File

@ -2,6 +2,7 @@
#import "ee_else_ce/work_items/list/queries/work_item_widgets.fragment.graphql"
query getWorkItems(
$includeDescendants: Boolean = true
$fullPath: ID!
$search: String
$sort: WorkItemSort
@ -9,9 +10,11 @@ query getWorkItems(
$assigneeWildcardId: AssigneeWildcardId
$assigneeUsernames: [String!]
$authorUsername: String
$confidential: Boolean
$labelName: [String!]
$milestoneTitle: [String!]
$milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String
$types: [IssueType!]
$in: [IssuableSearchableField!]
$not: NegatedWorkItemFilterInput
@ -24,15 +27,17 @@ query getWorkItems(
group(fullPath: $fullPath) {
id
workItemStateCounts(
includeDescendants: true
includeDescendants: $includeDescendants
sort: $sort
state: $state
assigneeUsernames: $assigneeUsernames
assigneeWildcardId: $assigneeWildcardId
authorUsername: $authorUsername
confidential: $confidential
labelName: $labelName
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
types: $types
not: $not
or: $or
@ -49,9 +54,11 @@ query getWorkItems(
assigneeUsernames: $assigneeUsernames
assigneeWildcardId: $assigneeWildcardId
authorUsername: $authorUsername
confidential: $confidential
labelName: $labelName
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
types: $types
in: $in
not: $not

View File

@ -101,7 +101,7 @@ module Repositories
Onboarding::ProgressService.async(project.namespace_id).execute(action: :git_pull)
return if Feature.enabled?(:disable_git_http_fetch_writes)
return if skip_fetch_statistics_increment?
Projects::FetchStatisticsIncrementService.new(project).execute
end
@ -140,6 +140,14 @@ module Repositories
payload[:metadata] ||= {}
payload[:metadata][:repository_storage] = project&.repository_storage
end
def skip_fetch_statistics_increment?
# Since disable_git_http_fetch_writes FF does not define a feature flag actor,
# it is currently not possible to increment the project statistics without enabling
# or disabling it for all projects. The allow_git_http_fetch_writes FF allow us to control this.
Feature.enabled?(:disable_git_http_fetch_writes) &&
Feature.disabled?(:allow_git_http_fetch_writes, project, type: :beta)
end
end
end

View File

@ -29,6 +29,7 @@ module WorkItemsHelper
def work_items_list_data(group, current_user)
{
autocomplete_award_emojis_path: autocomplete_award_emojis_path,
full_path: group.full_path,
initial_sort: current_user&.user_preference&.issues_sort,
is_signed_in: current_user.present?.to_s,

View File

@ -5,6 +5,7 @@ module Users
include IgnorableColumns
RELEASE_DAY = Date.new(2021, 5, 17)
DAILY_VERIFICATION_LIMIT = 5
self.table_name = 'user_credit_card_validations'
@ -82,5 +83,16 @@ module Users
def set_expiration_date_hash
self.expiration_date_hash = Gitlab::CryptoHelper.sha256(expiration_date.to_s)
end
def exceeded_daily_verification_limit?
return false unless Feature.enabled?(:credit_card_validation_daily_limit, user, type: :gitlab_com_derisk)
duplicate_record_count = self.class
.where(stripe_card_fingerprint: stripe_card_fingerprint)
.where('credit_card_validated_at > ?', 24.hours.ago)
.count
duplicate_record_count >= DAILY_VERIFICATION_LIMIT
end
end
end

View File

@ -11,7 +11,7 @@ module Users
def execute
credit_card = Users::CreditCardValidation.find_or_initialize_by_user(user_id)
credit_card_params = {
credit_card_attributes = {
credit_card_validated_at: credit_card_validated_at,
last_digits: last_digits,
holder_name: holder_name,
@ -23,7 +23,11 @@ module Users
stripe_card_fingerprint: stripe_card_fingerprint
}
credit_card.update!(credit_card_params)
credit_card.assign_attributes(credit_card_attributes)
return blocked if credit_card.exceeded_daily_verification_limit?
credit_card.save!
success
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::NotNullViolation, ActiveRecord::RecordInvalid
@ -85,5 +89,9 @@ module Users
def error
ServiceResponse.error(message: _('Error saving credit card validation record'))
end
def blocked
ServiceResponse.error(message: 'Credit card verification limit exceeded', reason: :rate_limited)
end
end
end

View File

@ -0,0 +1,9 @@
---
name: allow_git_http_fetch_writes
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/426270
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159835
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/473133
milestone: '17.3'
group: group::source code
type: beta
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: credit_card_validation_daily_limit
feature_issue_url: https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/742
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159151
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/472122
milestone: '17.3'
group: group::anti-abuse
type: gitlab_com_derisk
default_enabled: false

View File

@ -1320,8 +1320,10 @@ module API
if service.success?
present user.credit_card_validation, with: Entities::UserCreditCardValidations
elsif service.reason == :rate_limited
render_api_error!(service.message, 400)
else
render_api_error!('400 Bad Request', 400)
bad_request!
end
end
@ -1342,13 +1344,13 @@ module API
attrs = declared_params(include_missing: false)
render_api_error!('400 Bad Request', 400) unless attrs
bad_request! unless attrs
service = ::UserPreferences::UpdateService.new(current_user, attrs).execute
if service.success?
present preferences, with: Entities::UserPreferences
else
render_api_error!('400 Bad Request', 400)
bad_request!
end
end

View File

@ -8428,6 +8428,9 @@ msgstr ""
msgid "BillingPlan|Upgrade"
msgstr ""
msgid "Billings|Credit card has exceeded the daily verification limit. Use a different card or try again later."
msgstr ""
msgid "Billings|Error validating card details"
msgstr ""

View File

@ -26,6 +26,14 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
end
shared_examples 'increments fetch statistics' do
it 'calls Projects::FetchStatisticsIncrementService service' do
expect(Projects::FetchStatisticsIncrementService).to receive(:new).with(project).and_call_original
send_request
end
end
context 'when repository container is a project' do
it_behaves_like described_class do
let(:container) { project }
@ -111,10 +119,46 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
stub_feature_flags(disable_git_http_fetch_writes: true)
end
it 'does not increment statistics' do
expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
context 'and allow_git_http_fetch_writes is disabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: false)
end
send_request
it 'does not increment statistics' do
expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
send_request
end
end
context 'and allow_git_http_fetch_writes is enabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: true)
end
it_behaves_like 'increments fetch statistics'
end
end
context 'when disable_git_http_fetch_writes is disabled' do
before do
stub_feature_flags(disable_git_http_fetch_writes: false)
end
context 'and allow_git_http_fetch_writes is disabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: false)
end
it_behaves_like 'increments fetch statistics'
end
context 'and allow_git_http_fetch_writes is enabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: true)
end
it_behaves_like 'increments fetch statistics'
end
end
end

View File

@ -9,7 +9,9 @@ RSpec.describe 'Work items list filters', :js, feature_category: :team_planning
let_it_be(:user2) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:sub_group) { create(:group, parent: group) }
let_it_be(:project) { create(:project, :public, group: group, developers: [user1, user2]) }
let_it_be(:sub_group_project) { create(:project, :public, group: sub_group, developers: [user1, user2]) }
let_it_be(:label1) { create(:label, project: project) }
let_it_be(:label2) { create(:label, project: project) }
@ -18,24 +20,38 @@ RSpec.describe 'Work items list filters', :js, feature_category: :team_planning
let_it_be(:milestone2) { create(:milestone, group: group, start_date: 2.days.from_now, due_date: 9.days.from_now) }
let_it_be(:incident) do
create(:incident, project: project, assignees: [user1], author: user1, labels: [label1], description: 'aaa')
create(:incident, project: project,
assignees: [user1],
author: user1,
description: 'aaa',
labels: [label1])
end
let_it_be(:issue) do
create(:issue, project: project, author: user1, labels: [label1, label2], milestone: milestone1, title: 'eee')
create(:issue, project: project,
author: user1,
labels: [label1, label2],
milestone: milestone1,
title: 'eee')
end
let_it_be(:task) do
create(:work_item, :task, project: project, assignees: [user2], author: user2, milestone: milestone2)
create(:work_item, :task, project: sub_group_project,
assignees: [user2],
author: user2,
confidential: true,
milestone: milestone2)
end
let_it_be(:award_emoji) { create(:award_emoji, :upvote, user: user1, awardable: issue) }
context 'for signed in user' do
before do
sign_in(user1)
visit group_work_items_path(group)
end
describe 'assignees' do
describe 'assignee' do
it 'filters', :aggregate_failures do
select_tokens 'Assignee', '=', user1.username, submit: true
@ -101,7 +117,33 @@ RSpec.describe 'Work items list filters', :js, feature_category: :team_planning
end
end
describe 'labels' do
describe 'confidential' do
it 'filters', :aggregate_failures do
select_tokens 'Confidential', 'Yes', submit: true
expect(page).to have_css('.issue', count: 1)
expect(page).to have_link(task.title)
click_button 'Clear'
select_tokens 'Confidential', 'No', submit: true
expect(page).to have_css('.issue', count: 2)
expect(page).to have_link(incident.title)
expect(page).to have_link(issue.title)
end
end
describe 'group' do
it 'filters', :aggregate_failures do
select_tokens 'Group', sub_group.name, submit: true
expect(page).to have_css('.issue', count: 1)
expect(page).to have_link(task.title)
end
end
describe 'label' do
it 'filters', :aggregate_failures do
select_tokens 'Label', '=', label1.title, submit: true
@ -141,7 +183,7 @@ RSpec.describe 'Work items list filters', :js, feature_category: :team_planning
end
end
describe 'milestones' do
describe 'milestone' do
it 'filters', :aggregate_failures do
select_tokens 'Milestone', '=', milestone1.title, submit: true
@ -187,6 +229,38 @@ RSpec.describe 'Work items list filters', :js, feature_category: :team_planning
end
end
describe 'my-reaction' do
it 'filters', :aggregate_failures do
select_tokens 'My-Reaction', '=', 'thumbsup', submit: true
expect(page).to have_css('.issue', count: 1)
expect(page).to have_link(issue.title)
click_button 'Clear'
select_tokens 'My-Reaction', '!=', 'thumbsup', submit: true
expect(page).to have_css('.issue', count: 2)
expect(page).to have_link(incident.title)
expect(page).to have_link(task.title)
click_button 'Clear'
select_tokens 'My-Reaction', '=', 'None', submit: true
expect(page).to have_css('.issue', count: 2)
expect(page).to have_link(incident.title)
expect(page).to have_link(task.title)
click_button 'Clear'
select_tokens 'My-Reaction', '=', 'Any', submit: true
expect(page).to have_css('.issue', count: 1)
expect(page).to have_link(issue.title)
end
end
describe 'search within' do
it 'filters', :aggregate_failures do
select_tokens 'Search Within', 'Titles'

View File

@ -23,8 +23,11 @@ import {
OPERATOR_IS,
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_CONFIDENTIAL,
TOKEN_TYPE_GROUP,
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_SEARCH_WITHIN,
TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
@ -60,6 +63,7 @@ describe('WorkItemsListApp component', () => {
[setSortPreferenceMutation, sortPreferenceMutationResponse],
]),
provide: {
autocompleteAwardEmojisPath: 'autocomplete/award/emojis/path',
fullPath: 'full/path',
initialSort: CREATED_DESC,
isGroup: true,
@ -120,6 +124,7 @@ describe('WorkItemsListApp component', () => {
it('calls query to fetch work items', () => {
expect(defaultQueryHandler).toHaveBeenCalledWith({
fullPath: 'full/path',
includeDescendants: true,
sort: CREATED_DESC,
state: STATUS_OPEN,
firstPageSize: 20,
@ -154,6 +159,7 @@ describe('WorkItemsListApp component', () => {
expect(defaultQueryHandler).toHaveBeenCalledWith({
fullPath: 'full/path',
includeDescendants: true,
sort: CREATED_DESC,
state: STATUS_OPEN,
firstPageSize: 20,
@ -225,8 +231,11 @@ describe('WorkItemsListApp component', () => {
expect(findIssuableList().props('searchTokens')).toMatchObject([
{ type: TOKEN_TYPE_ASSIGNEE, preloadedUsers },
{ type: TOKEN_TYPE_AUTHOR, preloadedUsers },
{ type: TOKEN_TYPE_CONFIDENTIAL },
{ type: TOKEN_TYPE_GROUP },
{ type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_SEARCH_WITHIN },
{ type: TOKEN_TYPE_TYPE },
]);
@ -240,8 +249,11 @@ describe('WorkItemsListApp component', () => {
expect(findIssuableList().props('searchTokens')).toMatchObject([
{ type: TOKEN_TYPE_ASSIGNEE, preloadedUsers },
{ type: TOKEN_TYPE_AUTHOR, preloadedUsers },
{ type: TOKEN_TYPE_CONFIDENTIAL },
{ type: TOKEN_TYPE_GROUP },
{ type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_SEARCH_WITHIN },
]);
});
@ -276,6 +288,7 @@ describe('WorkItemsListApp component', () => {
expect(defaultQueryHandler).toHaveBeenCalledWith({
fullPath: 'full/path',
includeDescendants: true,
sort: CREATED_DESC,
state: STATUS_OPEN,
search: 'find issues',

View File

@ -102,6 +102,7 @@ RSpec.describe WorkItemsHelper, feature_category: :team_planning do
it 'returns expected data' do
expect(work_items_list_data).to include(
{
autocomplete_award_emojis_path: autocomplete_award_emojis_path,
full_path: group.full_path,
initial_sort: current_user&.user_preference&.issues_sort,
is_signed_in: current_user.present?.to_s,

View File

@ -317,5 +317,45 @@ RSpec.describe Users::CreditCardValidation, feature_category: :user_profile do
end
end
end
describe '#exceeded_daily_verification_limit?' do
let(:credit_card_validation) { build(:credit_card_validation) }
subject(:exceeded_limit?) { credit_card_validation.exceeded_daily_verification_limit? }
before do
stub_const("#{described_class}::DAILY_VERIFICATION_LIMIT", 1)
end
it { is_expected.to eq(false) }
context 'when the limit has been exceeded' do
before do
create(:credit_card_validation, stripe_card_fingerprint: credit_card_validation.stripe_card_fingerprint)
end
it { is_expected.to eq(true) }
context 'when the feature flag is disabled' do
before do
stub_feature_flags(credit_card_validation_daily_limit: false)
end
it { is_expected.to eq(false) }
end
end
context 'when the limit is exceeded but records have credit_card_validated_at > 24 hours' do
before do
create(
:credit_card_validation,
stripe_card_fingerprint: credit_card_validation.stripe_card_fingerprint,
credit_card_validated_at: 25.hours.ago
)
end
it { is_expected.to eq(false) }
end
end
end
end

View File

@ -2205,6 +2205,35 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_manageme
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
context 'when the credit card daily verification limit has been exceeded' do
before do
stub_const("Users::CreditCardValidation::DAILY_VERIFICATION_LIMIT", 1)
create(:credit_card_validation, stripe_card_fingerprint: stripe_card_fingerprint)
end
it "returns a 400 error with the reason" do
put api(path, admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('Credit card verification limit exceeded')
end
end
context 'when UpsertCreditCardValidationService returns an unexpected error' do
before do
allow_next_instance_of(::Users::UpsertCreditCardValidationService) do |instance|
allow(instance).to receive(:execute).and_return(ServiceResponse.error(message: 'upsert failed'))
end
end
it "returns a generic 400 error" do
put api(path, admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('400 Bad request')
end
end
end
end

View File

@ -198,5 +198,21 @@ RSpec.describe Users::UpsertCreditCardValidationService, feature_category: :user
expect(service_result.message).to eq(_('Error saving credit card validation record'))
end
end
context 'when the credit card verification limit has been reached' do
before do
allow_next_instance_of(Users::CreditCardValidation) do |instance|
allow(instance).to receive(:exceeded_daily_verification_limit?).and_return(true)
end
end
it 'returns an error', :aggregate_failures do
service_result = service.execute
expect(service_result).to be_error
expect(service_result.message).to eq('Credit card verification limit exceeded')
expect(service_result.reason).to eq(:rate_limited)
end
end
end
end