Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-24 15:07:28 +00:00
parent b4e7d9d839
commit 8a37720edf
57 changed files with 598 additions and 972 deletions

View File

@ -440,7 +440,7 @@ Rails/ApplicationController:
- 'app/controllers/health_controller.rb'
- 'app/controllers/metrics_controller.rb'
- 'ee/app/controllers/oauth/geo_auth_controller.rb'
- 'ee/spec/helpers/ee/services_helper_spec.rb'
- 'ee/spec/helpers/ee/integrations_helper_spec.rb'
- 'lib/gitlab/base_doorkeeper_controller.rb'
- 'lib/gitlab/request_forgery_protection.rb'
- 'spec/controllers/concerns/continue_params_spec.rb'

View File

@ -480,7 +480,7 @@ end
gem 'spamcheck', '~> 0.1.0'
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 14.0.0.pre.rc2'
gem 'gitaly', '~> 14.1.0.pre.rc1'
# KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2'

View File

@ -107,7 +107,7 @@ GEM
attr_required (1.0.1)
autoprefixer-rails (10.2.0.0)
execjs
awesome_print (1.8.0)
awesome_print (1.9.2)
awrence (1.1.1)
aws-eventstream (1.1.0)
aws-partitions (1.345.0)
@ -454,7 +454,7 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
gitaly (14.0.0.pre.rc2)
gitaly (14.1.0.pre.rc1)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab (4.16.1)
@ -1483,7 +1483,7 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 14.0.0.pre.rc2)
gitaly (~> 14.1.0.pre.rc1)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 2.1.2)

View File

@ -1,16 +1,6 @@
<script>
import {
GlButton,
GlBadge,
GlTooltip,
GlTooltipDirective,
GlFormTextarea,
GlFormCheckbox,
GlSprintf,
GlIcon,
GlToggle,
} from '@gitlab/ui';
import { memoize, isString, cloneDeep, isNumber, uniqueId } from 'lodash';
import { GlButton } from '@gitlab/ui';
import { memoize, cloneDeep, isNumber, uniqueId } from 'lodash';
import Vue from 'vue';
import { s__ } from '~/locale';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
@ -20,12 +10,8 @@ import {
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ALL_ENVIRONMENTS_NAME,
INTERNAL_ID_PREFIX,
NEW_VERSION_FLAG,
LEGACY_FLAG,
} from '../constants';
import { createNewEnvironmentScope } from '../store/helpers';
import EnvironmentsDropdown from './environments_dropdown.vue';
import Strategy from './strategy.vue';
export default {
@ -35,20 +21,9 @@ export default {
},
components: {
GlButton,
GlBadge,
GlFormTextarea,
GlFormCheckbox,
GlTooltip,
GlSprintf,
GlIcon,
GlToggle,
EnvironmentsDropdown,
Strategy,
RelatedIssuesRoot,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [featureFlagsMixin()],
inject: {
featureFlagIssuesEndpoint: {
@ -71,11 +46,6 @@ export default {
required: false,
default: '',
},
scopes: {
type: Array,
required: false,
default: () => [],
},
cancelPath: {
type: String,
required: true,
@ -89,11 +59,6 @@ export default {
required: false,
default: () => [],
},
version: {
type: String,
required: false,
default: LEGACY_FLAG,
},
},
translations: {
allEnvironmentsText: s__('FeatureFlags|* (All Environments)'),
@ -120,35 +85,18 @@ export default {
formName: this.name,
formDescription: this.description,
// operate on a clone to avoid mutating props
formScopes: this.scopes.map((s) => ({ ...s })),
formStrategies: cloneDeep(this.strategies),
newScope: '',
};
},
computed: {
filteredScopes() {
return this.formScopes.filter((scope) => !scope.shouldBeDestroyed);
},
filteredStrategies() {
return this.formStrategies.filter((s) => !s.shouldBeDestroyed);
},
canUpdateFlag() {
return !this.permissionsFlag || (this.formScopes || []).every((scope) => scope.canUpdate);
},
permissionsFlag() {
return this.glFeatures.featureFlagPermissions;
},
supportsStrategies() {
return this.version === NEW_VERSION_FLAG;
},
showRelatedIssues() {
return this.featureFlagIssuesEndpoint.length > 0;
},
readOnly() {
return this.version === LEGACY_FLAG;
},
},
methods: {
keyFor(strategy) {
@ -174,37 +122,6 @@ export default {
isAllEnvironment(name) {
return name === ALL_ENVIRONMENTS_NAME;
},
/**
* When the user clicks the remove button we delete the scope
*
* If the scope has an ID, we need to add the `shouldBeDestroyed` flag.
* If the scope does *not* have an ID, we can just remove it.
*
* This flag will be used when submitting the data to the backend
* to determine which records to delete (via a "_destroy" property).
*
* @param {Object} scope
*/
removeScope(scope) {
if (isString(scope.id) && scope.id.startsWith(INTERNAL_ID_PREFIX)) {
this.formScopes = this.formScopes.filter((s) => s !== scope);
} else {
Vue.set(scope, 'shouldBeDestroyed', true);
}
},
/**
* Creates a new scope and adds it to the list of scopes
*
* @param overrides An object whose properties will
* be used override the default scope options
*/
createNewScope(overrides) {
this.formScopes.push(createNewEnvironmentScope(overrides, this.permissionsFlag));
this.newScope = '';
},
/**
* When the user clicks the submit button
* it triggers an event with the form data
@ -214,61 +131,16 @@ export default {
name: this.formName,
description: this.formDescription,
active: this.active,
version: this.version,
version: NEW_VERSION_FLAG,
strategies: this.formStrategies,
};
if (this.version === LEGACY_FLAG) {
flag.scopes = this.formScopes;
} else {
flag.strategies = this.formStrategies;
}
this.$emit('handleSubmit', flag);
},
canUpdateScope(scope) {
return !this.permissionsFlag || scope.canUpdate;
},
isRolloutPercentageInvalid: memoize(function isRolloutPercentageInvalid(percentage) {
return !this.$options.rolloutPercentageRegex.test(percentage);
}),
/**
* Generates a unique ID for the strategy based on the v-for index
*
* @param index The index of the strategy
*/
rolloutStrategyId(index) {
return `rollout-strategy-${index}`;
},
/**
* Generates a unique ID for the percentage based on the v-for index
*
* @param index The index of the percentage
*/
rolloutPercentageId(index) {
return `rollout-percentage-${index}`;
},
rolloutUserId(index) {
return `rollout-user-id-${index}`;
},
shouldDisplayIncludeUserIds(scope) {
return ![ROLLOUT_STRATEGY_ALL_USERS, ROLLOUT_STRATEGY_USER_ID].includes(
scope.rolloutStrategy,
);
},
shouldDisplayUserIds(scope) {
return scope.rolloutStrategy === ROLLOUT_STRATEGY_USER_ID || scope.shouldIncludeUserIds;
},
onStrategyChange(index) {
const scope = this.filteredScopes[index];
scope.shouldIncludeUserIds =
scope.rolloutUserIds.length > 0 &&
scope.rolloutStrategy === ROLLOUT_STRATEGY_PERCENT_ROLLOUT;
},
onFormStrategyChange(strategy, index) {
Object.assign(this.filteredStrategies[index], strategy);
},
@ -281,12 +153,7 @@ export default {
<div class="row">
<div class="form-group col-md-4">
<label for="feature-flag-name" class="label-bold">{{ s__('FeatureFlags|Name') }} *</label>
<input
id="feature-flag-name"
v-model="formName"
:disabled="!canUpdateFlag"
class="form-control"
/>
<input id="feature-flag-name" v-model="formName" class="form-control" />
</div>
</div>
@ -298,7 +165,6 @@ export default {
<textarea
id="feature-flag-description"
v-model="formDescription"
:disabled="!canUpdateFlag"
class="form-control"
rows="4"
></textarea>
@ -312,277 +178,35 @@ export default {
:show-categorized-issues="false"
/>
<template v-if="supportsStrategies">
<div class="row">
<div class="col-md-12">
<h4>{{ s__('FeatureFlags|Strategies') }}</h4>
<div class="flex align-items-baseline justify-content-between">
<p class="mr-3">{{ $options.translations.newHelpText }}</p>
<gl-button variant="confirm" category="secondary" @click="addStrategy">
{{ s__('FeatureFlags|Add strategy') }}
</gl-button>
</div>
</div>
</div>
<div v-if="filteredStrategies.length > 0" data-testid="feature-flag-strategies">
<strategy
v-for="(strategy, index) in filteredStrategies"
:key="keyFor(strategy)"
:strategy="strategy"
:index="index"
@change="onFormStrategyChange($event, index)"
@delete="deleteStrategy(strategy)"
/>
</div>
<div v-else class="flex justify-content-center border-top py-4 w-100">
<span>{{ $options.translations.noStrategiesText }}</span>
</div>
</template>
<div v-else class="row">
<div class="form-group col-md-12">
<h4>{{ s__('FeatureFlags|Target environments') }}</h4>
<gl-sprintf :message="$options.translations.helpText">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
<template #bold="{ content }">
<b>{{ content }}</b>
</template>
</gl-sprintf>
<div class="js-scopes-table gl-mt-3">
<div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-30" role="columnheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div class="table-section section-20 text-center" role="columnheader">
{{ s__('FeatureFlags|Status') }}
</div>
<div class="table-section section-40" role="columnheader">
{{ s__('FeatureFlags|Rollout Strategy') }}
</div>
</div>
<div
v-for="(scope, index) in filteredScopes"
:key="scope.id"
ref="scopeRow"
class="gl-responsive-table-row"
role="row"
>
<div class="table-section section-30" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div
class="table-mobile-content gl-display-flex gl-align-items-center gl-justify-content-start"
>
<p v-if="isAllEnvironment(scope.environmentScope)" class="js-scope-all pl-3">
{{ $options.translations.allEnvironmentsText }}
</p>
<environments-dropdown
v-else
class="col-12"
:value="scope.environmentScope"
:disabled="!canUpdateScope(scope) || scope.environmentScope !== ''"
@selectEnvironment="(env) => (scope.environmentScope = env)"
@createClicked="(env) => (scope.environmentScope = env)"
@clearInput="(env) => (scope.environmentScope = '')"
/>
<gl-badge v-if="permissionsFlag && scope.protected" variant="success">
{{ s__('FeatureFlags|Protected') }}
</gl-badge>
</div>
</div>
<div class="table-section section-20 text-center" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ $options.i18n.statusLabel }}
</div>
<div class="table-mobile-content gl-display-flex gl-justify-content-center">
<gl-toggle
:value="scope.active"
:disabled="!active || !canUpdateScope(scope)"
:label="$options.i18n.statusLabel"
label-position="hidden"
@change="(status) => (scope.active = status)"
/>
</div>
</div>
<div class="table-section section-40" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Rollout Strategy') }}
</div>
<div class="table-mobile-content js-rollout-strategy form-inline">
<label class="sr-only" :for="rolloutStrategyId(index)">
{{ s__('FeatureFlags|Rollout Strategy') }}
</label>
<div class="select-wrapper col-12 col-md-8 p-0">
<select
:id="rolloutStrategyId(index)"
v-model="scope.rolloutStrategy"
:disabled="!scope.active"
class="form-control select-control w-100 js-rollout-strategy"
@change="onStrategyChange(index)"
>
<option :value="$options.ROLLOUT_STRATEGY_ALL_USERS">
{{ s__('FeatureFlags|All users') }}
</option>
<option :value="$options.ROLLOUT_STRATEGY_PERCENT_ROLLOUT">
{{ s__('FeatureFlags|Percent rollout (logged in users)') }}
</option>
<option :value="$options.ROLLOUT_STRATEGY_USER_ID">
{{ s__('FeatureFlags|User IDs') }}
</option>
</select>
<gl-icon
name="chevron-down"
class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500"
:size="16"
/>
</div>
<div
v-if="scope.rolloutStrategy === $options.ROLLOUT_STRATEGY_PERCENT_ROLLOUT"
class="d-flex-center mt-2 mt-md-0 ml-md-2"
>
<label class="sr-only" :for="rolloutPercentageId(index)">
{{ s__('FeatureFlags|Rollout Percentage') }}
</label>
<div class="gl-w-9">
<input
:id="rolloutPercentageId(index)"
v-model="scope.rolloutPercentage"
:disabled="!scope.active"
:class="{
'is-invalid': isRolloutPercentageInvalid(scope.rolloutPercentage),
}"
type="number"
min="0"
max="100"
:pattern="$options.rolloutPercentageRegex.source"
class="rollout-percentage js-rollout-percentage form-control text-right w-100"
/>
</div>
<gl-tooltip
v-if="isRolloutPercentageInvalid(scope.rolloutPercentage)"
:target="rolloutPercentageId(index)"
>
{{
s__(
'FeatureFlags|Percent rollout must be an integer number between 0 and 100',
)
}}
</gl-tooltip>
<span class="ml-1">%</span>
</div>
<div class="d-flex flex-column align-items-start mt-2 w-100">
<gl-form-checkbox
v-if="shouldDisplayIncludeUserIds(scope)"
v-model="scope.shouldIncludeUserIds"
>{{ s__('FeatureFlags|Include additional user IDs') }}</gl-form-checkbox
>
<template v-if="shouldDisplayUserIds(scope)">
<label :for="rolloutUserId(index)" class="mb-2">
{{ s__('FeatureFlags|User IDs') }}
</label>
<gl-form-textarea
:id="rolloutUserId(index)"
v-model="scope.rolloutUserIds"
class="w-100"
/>
</template>
</div>
</div>
</div>
<div class="table-section section-10 text-right" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Remove') }}
</div>
<div class="table-mobile-content">
<gl-button
v-if="!isAllEnvironment(scope.environmentScope) && canUpdateScope(scope)"
v-gl-tooltip
:title="$options.i18n.removeLabel"
:aria-label="$options.i18n.removeLabel"
class="js-delete-scope btn-transparent pr-3 pl-3"
icon="clear"
data-testid="feature-flag-delete"
@click="removeScope(scope)"
/>
</div>
</div>
</div>
<div class="gl-responsive-table-row" role="row" data-testid="add-new-scope">
<div class="table-section section-30" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div class="table-mobile-content">
<environments-dropdown
class="js-new-scope-name col-12"
:value="newScope"
@selectEnvironment="(env) => createNewScope({ environmentScope: env })"
@createClicked="(env) => createNewScope({ environmentScope: env })"
/>
</div>
</div>
<div class="table-section section-20 text-center" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ $options.i18n.statusLabel }}
</div>
<div class="table-mobile-content gl-display-flex gl-justify-content-center">
<gl-toggle
:disabled="!active"
:label="$options.i18n.statusLabel"
label-position="hidden"
:value="false"
@change="createNewScope({ active: true })"
/>
</div>
</div>
<div class="table-section section-40" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Rollout Strategy') }}
</div>
<div class="table-mobile-content js-rollout-strategy form-inline">
<label class="sr-only" for="new-rollout-strategy-placeholder">{{
s__('FeatureFlags|Rollout Strategy')
}}</label>
<div class="select-wrapper col-12 col-md-8 p-0">
<select
id="new-rollout-strategy-placeholder"
disabled
class="form-control select-control w-100"
>
<option>{{ s__('FeatureFlags|All users') }}</option>
</select>
<gl-icon
name="chevron-down"
class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500"
:size="16"
/>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h4>{{ s__('FeatureFlags|Strategies') }}</h4>
<div class="flex align-items-baseline justify-content-between">
<p class="mr-3">{{ $options.translations.newHelpText }}</p>
<gl-button variant="confirm" category="secondary" @click="addStrategy">
{{ s__('FeatureFlags|Add strategy') }}
</gl-button>
</div>
</div>
</div>
<div v-if="filteredStrategies.length > 0" data-testid="feature-flag-strategies">
<strategy
v-for="(strategy, index) in filteredStrategies"
:key="keyFor(strategy)"
:strategy="strategy"
:index="index"
@change="onFormStrategyChange($event, index)"
@delete="deleteStrategy(strategy)"
/>
</div>
<div v-else class="flex justify-content-center border-top py-4 w-100">
<span>{{ $options.translations.noStrategiesText }}</span>
</div>
</fieldset>
<div class="form-actions">
<gl-button
ref="submitButton"
:disabled="readOnly"
type="button"
variant="confirm"
class="js-ff-submit col-xs-12"

View File

@ -3,19 +3,19 @@ import { s__, __ } from '~/locale';
export const PACKAGE_SETTINGS_HEADER = s__('PackageRegistry|Package Registry');
export const PACKAGE_SETTINGS_DESCRIPTION = s__(
'PackageRegistry|GitLab Packages allows organizations to utilize GitLab as a private repository for a variety of common package formats. %{linkStart}More Information%{linkEnd}',
'PackageRegistry|Use GitLab as a private registry for common package formats. %{linkStart}Learn more.%{linkEnd}',
);
export const DUPLICATES_TOGGLE_LABEL = s__('PackageRegistry|Allow duplicates');
export const DUPLICATES_ALLOWED_DISABLED = s__(
'PackageRegistry|%{boldStart}Do not allow duplicates%{boldEnd} - Packages with the same name and version are rejected.',
'PackageRegistry|%{boldStart}Do not allow duplicates%{boldEnd} - Reject packages with the same name and version.',
);
export const DUPLICATES_ALLOWED_ENABLED = s__(
'PackageRegistry|%{boldStart}Allow duplicates%{boldEnd} - Packages with the same name and version are accepted.',
'PackageRegistry|%{boldStart}Allow duplicates%{boldEnd} - Accept packages with the same name and version.',
);
export const DUPLICATES_SETTING_EXCEPTION_TITLE = __('Exceptions');
export const DUPLICATES_SETTINGS_EXCEPTION_LEGEND = s__(
'PackageRegistry|Packages can be published if their name or version matches this regex',
'PackageRegistry|Publish packages if their name or version matches this regex.',
);
export const SUCCESS_UPDATING_SETTINGS = s__('PackageRegistry|Settings saved successfully');

View File

@ -87,6 +87,10 @@ $tabs-holder-z-index: 250;
border: 1px solid $border-color;
border-radius: $border-radius-default;
background: var(--white, $white);
> .mr-widget-border-top:first-of-type {
border-top: 0;
}
}
.mr-widget-body,

View File

@ -2,7 +2,7 @@
class Admin::ApplicationSettingsController < Admin::ApplicationController
include InternalRedirect
include ServicesHelper
include IntegrationsHelper
# NOTE: Use @application_setting in this controller when you need to access
# application_settings after it has been modified. This is because the

View File

@ -2,7 +2,7 @@
class Admin::IntegrationsController < Admin::ApplicationController
include IntegrationsActions
include ServicesHelper
include IntegrationsHelper
before_action :not_found, unless: -> { instance_level_integrations? }

View File

@ -18,6 +18,10 @@ module Resolvers
required: true,
description: 'The project of the CI config.'
argument :sha, GraphQL::STRING_TYPE,
required: false,
description: "Sha for the pipeline."
argument :content, GraphQL::STRING_TYPE,
required: true,
description: "Contents of `.gitlab-ci.yml`."
@ -26,11 +30,11 @@ module Resolvers
required: false,
description: 'Run pipeline creation simulation, or only do static check.'
def resolve(project_path:, content:, dry_run: false)
def resolve(project_path:, content:, sha: nil, dry_run: false)
project = authorized_find!(project_path: project_path)
result = ::Gitlab::Ci::Lint
.new(project: project, current_user: context[:current_user])
.new(project: project, current_user: context[:current_user], sha: sha)
.validate(content, dry_run: dry_run)
response(result).merge(merged_yaml: result.merged_yaml)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module ServicesHelper
def service_event_description(event)
module IntegrationsHelper
def integration_event_description(event)
case event
when "push", "push_events"
s_("ProjectService|Trigger event for pushes to the repository.")
@ -30,7 +30,7 @@ module ServicesHelper
end
end
def service_event_field_name(event)
def integration_event_field_name(event)
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events"
end
@ -96,8 +96,8 @@ module ServicesHelper
enable_comments: integration.comment_on_event_enabled.to_s,
comment_detail: integration.comment_detail,
learn_more_path: integrations_help_page_path,
trigger_events: trigger_events_for_service(integration),
fields: fields_for_service(integration),
trigger_events: trigger_events_for_integration(integration),
fields: fields_for_integration(integration),
inherit_from_id: integration.inherit_from_id,
integration_level: integration_level(integration),
editable: integration.editable?.to_s,
@ -121,14 +121,6 @@ module ServicesHelper
}
end
def trigger_events_for_service(integration)
ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
end
def fields_for_service(integration)
ServiceFieldSerializer.new(service: integration).represent(integration.global_fields).to_json
end
def integrations_help_page_path
help_page_path('user/admin_area/settings/project_integration_management')
end
@ -152,6 +144,14 @@ module ServicesHelper
private
def trigger_events_for_integration(integration)
ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
end
def fields_for_integration(integration)
ServiceFieldSerializer.new(service: integration).represent(integration.global_fields).to_json
end
def integration_level(integration)
if integration.instance_level?
'instance'
@ -178,8 +178,8 @@ module ServicesHelper
end
end
ServicesHelper.prepend_mod_with('ServicesHelper')
IntegrationsHelper.prepend_mod_with('IntegrationsHelper')
# The methods in `EE::ServicesHelper` should be available as both instance and
# The methods in `EE::IntegrationsHelper` should be available as both instance and
# class methods.
ServicesHelper.extend_mod_with('ServicesHelper')
IntegrationsHelper.extend_mod_with('IntegrationsHelper')

View File

@ -2,6 +2,7 @@
module OperationsHelper
include Gitlab::Utils::StrongMemoize
include IntegrationsHelper
def prometheus_integration
strong_memoize(:prometheus_integration) do

View File

@ -168,19 +168,6 @@ module SortingHelper
}
end
def member_sort_options_hash
{
sort_value_access_level_asc => sort_title_access_level_asc,
sort_value_access_level_desc => sort_title_access_level_desc,
sort_value_last_joined => sort_title_last_joined,
sort_value_name => sort_title_name_asc,
sort_value_name_desc => sort_title_name_desc,
sort_value_oldest_joined => sort_title_oldest_joined,
sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_recently_signin => sort_title_recently_signin
}
end
def sortable_item(item, path, sorted_by)
link_to item, path, class: sorted_by == item ? 'is-active' : ''
end

View File

@ -2,14 +2,6 @@
module SortingTitlesValuesHelper
# Titles.
def sort_title_access_level_asc
s_('SortOptions|Access level, ascending')
end
def sort_title_access_level_desc
s_('SortOptions|Access level, descending')
end
def sort_title_created_date
s_('SortOptions|Created date')
end
@ -42,10 +34,6 @@ module SortingTitlesValuesHelper
s_('SortOptions|Largest repository')
end
def sort_title_last_joined
s_('SortOptions|Last joined')
end
def sort_title_latest_activity
s_('SortOptions|Last updated')
end
@ -82,10 +70,6 @@ module SortingTitlesValuesHelper
s_('SortOptions|Oldest created')
end
def sort_title_oldest_joined
s_('SortOptions|Oldest joined')
end
def sort_title_oldest_signin
s_('SortOptions|Oldest sign in')
end
@ -167,14 +151,6 @@ module SortingTitlesValuesHelper
end
# Values.
def sort_value_access_level_asc
'access_level_asc'
end
def sort_value_access_level_desc
'access_level_desc'
end
def sort_value_created_date
'created_date'
end
@ -207,10 +183,6 @@ module SortingTitlesValuesHelper
'storage_size_desc'
end
def sort_value_last_joined
'last_joined'
end
def sort_value_latest_activity
'latest_activity_desc'
end
@ -247,10 +219,6 @@ module SortingTitlesValuesHelper
'oldest_sign_in'
end
def sort_value_oldest_joined
'oldest_joined'
end
def sort_value_oldest_updated
'updated_asc'
end

View File

@ -577,11 +577,11 @@ module Ci
canceled? && auto_canceled_by_id?
end
def cancel_running(retries: nil)
def cancel_running(retries: 1)
commit_status_relations = [:project, :pipeline]
ci_build_relations = [:deployment, :taggings]
retry_optimistic_lock(cancelable_statuses, retries, name: 'ci_pipeline_cancel_running') do |cancelables|
retry_lock(cancelable_statuses, retries, name: 'ci_pipeline_cancel_running') do |cancelables|
cancelables.find_in_batches do |batch|
ActiveRecord::Associations::Preloader.new.preload(batch, commit_status_relations)
ActiveRecord::Associations::Preloader.new.preload(batch.select { |job| job.is_a?(Ci::Build) }, ci_build_relations)
@ -594,7 +594,7 @@ module Ci
end
end
def auto_cancel_running(pipeline, retries: nil)
def auto_cancel_running(pipeline, retries: 1)
update(auto_canceled_by: pipeline)
cancel_running(retries: retries) do |job|

View File

@ -165,9 +165,13 @@ class Integration < ApplicationRecord
args.each do |arg|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{arg}
Gitlab::Utils.to_boolean(properties['#{arg}'])
end
def #{arg}?
# '!!' is used because nil or empty string is converted to nil
!!ActiveRecord::Type::Boolean.new.cast(#{arg})
!!#{arg}
end
RUBY
end
@ -178,7 +182,7 @@ class Integration < ApplicationRecord
end
def self.event_names
self.supported_events.map { |event| ServicesHelper.service_event_field_name(event) }
self.supported_events.map { |event| IntegrationsHelper.integration_event_field_name(event) }
end
def self.supported_event_actions
@ -194,7 +198,7 @@ class Integration < ApplicationRecord
end
def self.event_description(event)
ServicesHelper.service_event_description(event)
IntegrationsHelper.integration_event_description(event)
end
def self.find_or_create_templates

View File

@ -10,11 +10,11 @@ class ServiceEventEntity < Grape::Entity
expose :event_field_name, as: :name
expose :value do |event|
service[event_field_name]
integration[event_field_name]
end
expose :description do |event|
ServicesHelper.service_event_description(event)
IntegrationsHelper.integration_event_description(event)
end
expose :field, if: -> (_, _) { event_field } do
@ -22,7 +22,7 @@ class ServiceEventEntity < Grape::Entity
event_field[:name]
end
expose :value do |event|
service.public_send(event_field[:name]) # rubocop:disable GitlabSecurity/PublicSend
integration.public_send(event_field[:name]) # rubocop:disable GitlabSecurity/PublicSend
end
end
@ -31,14 +31,14 @@ class ServiceEventEntity < Grape::Entity
alias_method :event, :object
def event_field_name
ServicesHelper.service_event_field_name(event)
IntegrationsHelper.integration_event_field_name(event)
end
def event_field
@event_field ||= service.event_field(event)
@event_field ||= integration.event_field(event)
end
def service
def integration
request.service
end
end

View File

@ -1,19 +0,0 @@
.dropdown.inline.qa-user-sort-dropdown{ data: { testid: 'user-sort-dropdown' } }
= dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown', testid: 'dropdown-toggle' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header
= _("Sort by")
- member_sort_options_hash.each do |value, title|
%li
= link_to filter_group_project_member_path(sort: value), class: ("is-active" if @sort == value) do
= title
%li.divider
%li{ data: { testid: 'filter-members-with-inherited-permissions' } }
= link_to filter_group_project_member_path(with_inherited_permissions: nil), class: ("is-active" unless params[:with_inherited_permissions].present?) do
= _("Show all members")
%li{ data: { testid: 'filter-members-with-inherited-permissions' } }
= link_to filter_group_project_member_path(with_inherited_permissions: 'exclude'), class: ("is-active" if params[:with_inherited_permissions] == 'exclude') do
= _("Show only direct members")
%li{ data: { testid: 'filter-members-with-inherited-permissions' } }
= link_to filter_group_project_member_path(with_inherited_permissions: 'only'), class: ("is-active" if params[:with_inherited_permissions] == 'only') do
= _("Show only inherited members")

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329194
milestone: '13.12'
type: development
group: group::pipeline authoring
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: git_access_batched_changes_size
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64503
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334130
milestone: '14.1'
type: development
group: group::gitaly
default_enabled: false

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
class ScheduleLatestPipelineIdPopulation < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
DELAY_INTERVAL = 2.minutes.to_i
BATCH_SIZE = 100
MIGRATION = 'PopulateLatestPipelineIds'
disable_ddl_transaction!
def up
return unless Gitlab.ee?
queue_background_migration_jobs_by_range_at_intervals(
Gitlab::BackgroundMigration::PopulateLatestPipelineIds::ProjectSetting.has_vulnerabilities_without_latest_pipeline_set,
MIGRATION,
DELAY_INTERVAL,
batch_size: BATCH_SIZE,
primary_column_name: 'project_id'
)
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
6c617b919e6e0cba0bd62cc0d5056dcad3ebe1a9ce25102a288de5456cbaa6c3

View File

@ -52,6 +52,7 @@ Returns [`CiConfig`](#ciconfig).
| <a id="queryciconfigcontent"></a>`content` | [`String!`](#string) | Contents of `.gitlab-ci.yml`. |
| <a id="queryciconfigdryrun"></a>`dryRun` | [`Boolean`](#boolean) | Run pipeline creation simulation, or only do static check. |
| <a id="queryciconfigprojectpath"></a>`projectPath` | [`ID!`](#id) | The project of the CI config. |
| <a id="queryciconfigsha"></a>`sha` | [`String`](#string) | Sha for the pipeline. |
### `Query.containerRepository`

View File

@ -118,8 +118,8 @@ the related documentation.
GitLab.com has the following [account limits](../admin_area/settings/account_and_limit_settings.md)
enabled. If a setting is not listed, it is set to the default value.
If you are near or over the repository size limit, you can
[reduce your repository size with Git](../project/repository/reducing_the_repo_size_using_git.md).
If you are near or over the repository size limit, you can either
[reduce your repository size with Git](../project/repository/reducing_the_repo_size_using_git.md) or [purchase additional storage](https://about.gitlab.com/pricing/licensing-faq/#can-i-buy-more-storage).
| Setting | GitLab.com | Default |
|-------------------------------|------------|---------|

View File

@ -87,7 +87,3 @@ To add a sub-group to your Group DevOps Adoption report:
The sub-group data might not appear immediately, because GitLab requires around a minute to collect
the data.
Please note that the sub-group data might not appear immediately,
because GitLab requires a few moments to collect the data.
Generally the data will be visible in less than one minute.

View File

@ -6,10 +6,18 @@ module API
# Expose serialized properties
expose :properties do |service, options|
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
if service.data_fields_present?
service.data_fields.as_json.slice(*service.api_field_names)
else
service.properties.slice(*service.api_field_names)
attributes =
if service.data_fields_present?
service.data_fields.as_json.keys
else
service.properties.keys
end
attributes &= service.api_field_names
attributes.each_with_object({}) do |attribute, hash|
hash[attribute] = service.public_send(attribute) # rubocop:disable GitlabSecurity/PublicSend
end
end
end

View File

@ -6,7 +6,7 @@ module API
#
# The data structures inside this model are returned using class methods,
# allowing EE to extend them where necessary.
module ServicesHelpers
module IntegrationsHelpers
def self.chat_notification_settings
[
{
@ -159,7 +159,7 @@ module API
].freeze
end
def self.services
def self.integrations
{
'asana' => [
{
@ -772,7 +772,7 @@ module API
}
end
def self.service_classes
def self.integration_classes
[
::Integrations::Asana,
::Integrations::Assembla,
@ -809,7 +809,7 @@ module API
]
end
def self.development_service_classes
def self.development_integration_classes
[
::Integrations::MockCi,
::Integrations::MockMonitoring
@ -819,4 +819,4 @@ module API
end
end
API::Helpers::ServicesHelpers.prepend_mod_with('API::Helpers::ServicesHelpers')
API::Helpers::IntegrationsHelpers.prepend_mod_with('API::Helpers::IntegrationsHelpers')

View File

@ -3,11 +3,11 @@ module API
class Services < ::API::Base
feature_category :integrations
services = Helpers::ServicesHelpers.services
service_classes = Helpers::ServicesHelpers.service_classes
integrations = Helpers::IntegrationsHelpers.integrations
integration_classes = Helpers::IntegrationsHelpers.integration_classes
if Rails.env.development?
services['mock-ci'] = [
integrations['mock-ci'] = [
{
required: true,
name: :mock_service_url,
@ -15,19 +15,18 @@ module API
desc: 'URL to the mock service'
}
]
services['mock-deployment'] = []
services['mock-monitoring'] = []
integrations['mock-deployment'] = []
integrations['mock-monitoring'] = []
service_classes += Helpers::ServicesHelpers.development_service_classes
integration_classes += Helpers::IntegrationsHelpers.development_integration_classes
end
SERVICES = services.freeze
SERVICE_CLASSES = service_classes.freeze
INTEGRATIONS = integrations.freeze
SERVICE_CLASSES.each do |service|
integration_classes.each do |service|
event_names = service.try(:event_names) || next
event_names.each do |event_name|
SERVICES[service.to_param.tr("_", "-")] << {
INTEGRATIONS[service.to_param.tr("_", "-")] << {
required: false,
name: event_name.to_sym,
type: String,
@ -77,7 +76,7 @@ module API
present services, with: Entities::ProjectServiceBasic
end
SERVICES.each do |slug, settings|
INTEGRATIONS.each do |slug, settings|
desc "Set #{slug} service for project"
params do
settings.each do |setting|
@ -100,9 +99,9 @@ module API
end
end
desc "Delete a service for project"
desc "Delete an integration from a project"
params do
requires :slug, type: String, values: SERVICES.keys, desc: 'The name of the service'
requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the service'
end
delete ":id/services/:slug" do
integration = user_project.find_or_initialize_integration(params[:slug].underscore)
@ -114,11 +113,11 @@ module API
end
end
desc 'Get the service settings for project' do
desc 'Get the integration settings for a project' do
success Entities::ProjectService
end
params do
requires :slug, type: String, values: SERVICES.keys, desc: 'The name of the service'
requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the service'
end
get ":id/services/:slug" do
integration = user_project.find_or_initialize_integration(params[:slug].underscore)

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
# rubocop: disable Style/Documentation
module Gitlab
module BackgroundMigration
class PopulateLatestPipelineIds
class ProjectSetting < ActiveRecord::Base
include EachBatch
self.table_name = 'project_settings'
scope :in_range, -> (start_id, end_id) { where(id: start_id..end_id) }
scope :has_vulnerabilities_without_latest_pipeline_set, -> do
joins('LEFT OUTER JOIN vulnerability_statistics vs ON vs.project_id = project_settings.project_id')
.where(vs: { latest_pipeline_id: nil })
.where('has_vulnerabilities IS TRUE')
end
end
def perform(start_id, end_id)
# no-op
end
end
end
end
Gitlab::BackgroundMigration::PopulateLatestPipelineIds.prepend_mod

View File

@ -107,7 +107,10 @@ module Gitlab
batch_counter = 0
model_class.each_batch(of: batch_size) do |relation, index|
start_id, end_id = relation.pluck(Arel.sql("MIN(#{primary_column_name}), MAX(#{primary_column_name})")).first
max = relation.arel_table[primary_column_name].maximum
min = relation.arel_table[primary_column_name].minimum
start_id, end_id = relation.pluck(min, max).first
# `BackgroundMigrationWorker.bulk_perform_in` schedules all jobs for
# the same time, which is not helpful in most cases where we wish to

View File

@ -370,6 +370,20 @@ module Gitlab
end
end
# List blobs reachable via a set of revisions. Supports the
# pseudo-revisions `--not` and `--all`. Uses the minimum of
# GitalyClient.medium_timeout and dynamic timeout if the dynamic
# timeout is set, otherwise it'll always use the medium timeout.
def blobs(revisions, dynamic_timeout: nil)
revisions = revisions.reject { |rev| rev.blank? || rev == ::Gitlab::Git::BLANK_SHA }
return [] if revisions.blank?
wrapped_gitaly_errors do
gitaly_blob_client.list_blobs(revisions, limit: REV_LIST_COMMIT_LIMIT, dynamic_timeout: dynamic_timeout)
end
end
def count_commits(options)
options = process_count_commits_options(options.dup)

View File

@ -498,13 +498,23 @@ module Gitlab
end
def check_changes_size
changes_size = 0
changes_size =
if Feature.enabled?(:git_access_batched_changes_size, project, default_enabled: :yaml)
revs = ['--not', '--all', '--not']
revs += changes_list.map { |change| change[:newrev] }
changes_list.each do |change|
changes_size += repository.new_blobs(change[:newrev]).sum(&:size)
repository.blobs(revs).sum(&:size)
else
changes_size = 0
check_size_against_limit(changes_size)
end
changes_list.each do |change|
changes_size += repository.new_blobs(change[:newrev]).sum(&:size)
end
changes_size
end
check_size_against_limit(changes_size)
end
def check_size_against_limit(size)

View File

@ -19,6 +19,25 @@ module Gitlab
consume_blob_response(response)
end
def list_blobs(revisions, limit: 0, bytes_limit: 0, dynamic_timeout: nil)
request = Gitaly::ListBlobsRequest.new(
repository: @gitaly_repo,
revisions: Array.wrap(revisions),
limit: limit,
bytes_limit: bytes_limit
)
timeout =
if dynamic_timeout
[dynamic_timeout, GitalyClient.medium_timeout].min
else
GitalyClient.medium_timeout
end
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :list_blobs, request, timeout: timeout)
GitalyClient::BlobsStitcher.new(GitalyClient::ListBlobsAdapter.new(response))
end
def batch_lfs_pointers(blob_ids)
return [] if blob_ids.empty?

View File

@ -35,8 +35,8 @@ module Gitlab
Gitlab::Git::Blob.new(
id: blob_data[:oid],
mode: blob_data[:mode].to_s(8),
name: File.basename(blob_data[:path]),
mode: blob_data[:mode]&.to_s(8),
name: blob_data[:path] && File.basename(blob_data[:path]),
path: blob_data[:path],
size: blob_data[:size],
commit_id: blob_data[:revision],

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Gitlab
module GitalyClient
class ListBlobsAdapter
include Enumerable
def initialize(rpc_response)
@rpc_response = rpc_response
end
def each
@rpc_response.each do |msg|
msg.blobs.each do |blob|
yield blob
end
end
end
end
end
end

View File

@ -13610,9 +13610,6 @@ msgstr ""
msgid "FeatureFlags|Enable features for specific users and environments by configuring feature flag strategies."
msgstr ""
msgid "FeatureFlags|Environment Spec"
msgstr ""
msgid "FeatureFlags|Environment Specs"
msgstr ""
@ -13658,9 +13655,6 @@ msgstr ""
msgid "FeatureFlags|Inactive flag for %{scope}"
msgstr ""
msgid "FeatureFlags|Include additional user IDs"
msgstr ""
msgid "FeatureFlags|Install a %{docsLinkAnchoredStart}compatible client library%{docsLinkAnchoredEnd} and specify the API URL, application name, and instance ID during the configuration setup. %{docsLinkStart}More Information%{docsLinkEnd}"
msgstr ""
@ -13700,24 +13694,12 @@ msgstr ""
msgid "FeatureFlags|Percent rollout"
msgstr ""
msgid "FeatureFlags|Percent rollout (logged in users)"
msgstr ""
msgid "FeatureFlags|Percent rollout must be an integer number between 0 and 100"
msgstr ""
msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Remove"
msgstr ""
msgid "FeatureFlags|Rollout Percentage"
msgstr ""
msgid "FeatureFlags|Rollout Strategy"
msgstr ""
msgid "FeatureFlags|Set the Unleash client application name to the name of the environment your application runs in. This value is used to match environment scopes. See the %{linkStart}example client configuration%{linkEnd}."
msgstr ""
@ -13727,9 +13709,6 @@ msgstr ""
msgid "FeatureFlags|Strategies"
msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
@ -23090,10 +23069,10 @@ msgstr ""
msgid "Package type must be RubyGems"
msgstr ""
msgid "PackageRegistry|%{boldStart}Allow duplicates%{boldEnd} - Packages with the same name and version are accepted."
msgid "PackageRegistry|%{boldStart}Allow duplicates%{boldEnd} - Accept packages with the same name and version."
msgstr ""
msgid "PackageRegistry|%{boldStart}Do not allow duplicates%{boldEnd} - Packages with the same name and version are rejected."
msgid "PackageRegistry|%{boldStart}Do not allow duplicates%{boldEnd} - Reject packages with the same name and version."
msgstr ""
msgid "PackageRegistry|%{name} version %{version} was first created %{datetime}"
@ -23234,9 +23213,6 @@ msgstr ""
msgid "PackageRegistry|Generic"
msgstr ""
msgid "PackageRegistry|GitLab Packages allows organizations to utilize GitLab as a private repository for a variety of common package formats. %{linkStart}More Information%{linkEnd}"
msgstr ""
msgid "PackageRegistry|Gradle Groovy DSL"
msgstr ""
@ -23300,15 +23276,15 @@ msgstr ""
msgid "PackageRegistry|Package updated by commit %{link} on branch %{branch}, built by pipeline %{pipeline}, and published to the registry %{datetime}"
msgstr ""
msgid "PackageRegistry|Packages can be published if their name or version matches this regex"
msgstr ""
msgid "PackageRegistry|Pip Command"
msgstr ""
msgid "PackageRegistry|Publish and share packages for a variety of common package managers. %{docLinkStart}More information%{docLinkEnd}"
msgstr ""
msgid "PackageRegistry|Publish packages if their name or version matches this regex."
msgstr ""
msgid "PackageRegistry|Published to the %{project} Package Registry %{datetime}"
msgstr ""
@ -23390,6 +23366,9 @@ msgstr ""
msgid "PackageRegistry|Unable to load package"
msgstr ""
msgid "PackageRegistry|Use GitLab as a private registry for common package formats. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "PackageRegistry|You are about to delete %{filename}. This is a destructive action that may render your package unusable. Are you sure?"
msgstr ""
@ -29807,9 +29786,6 @@ msgstr ""
msgid "Show all issues."
msgstr ""
msgid "Show all members"
msgstr ""
msgid "Show all test cases."
msgstr ""
@ -29870,12 +29846,6 @@ msgstr ""
msgid "Show one file at a time"
msgstr ""
msgid "Show only direct members"
msgstr ""
msgid "Show only inherited members"
msgstr ""
msgid "Show parent pages"
msgstr ""
@ -30426,12 +30396,6 @@ msgstr ""
msgid "Sort direction: Descending"
msgstr ""
msgid "SortOptions|Access level, ascending"
msgstr ""
msgid "SortOptions|Access level, descending"
msgstr ""
msgid "SortOptions|Blocking"
msgstr ""
@ -30465,9 +30429,6 @@ msgstr ""
msgid "SortOptions|Last created"
msgstr ""
msgid "SortOptions|Last joined"
msgstr ""
msgid "SortOptions|Last updated"
msgstr ""
@ -30510,9 +30471,6 @@ msgstr ""
msgid "SortOptions|Oldest created"
msgstr ""
msgid "SortOptions|Oldest joined"
msgstr ""
msgid "SortOptions|Oldest last activity"
msgstr ""

View File

@ -48,6 +48,6 @@ RSpec.describe 'User searches group settings', :js do
visit group_settings_packages_and_registries_path(group)
end
it_behaves_like 'can highlight results', 'GitLab Packages'
it_behaves_like 'can highlight results', 'Use GitLab as a private registry'
end
end

View File

@ -115,7 +115,7 @@ describe('Edit feature flag form', () => {
});
it('should set the version of the form from the feature flag', () => {
expect(wrapper.find(Form).props('version')).toBe(LEGACY_FLAG);
expect(wrapper.find(Form).attributes('version')).toBe(LEGACY_FLAG);
mock.resetHandlers();
@ -136,7 +136,7 @@ describe('Edit feature flag form', () => {
factory();
return axios.waitForAll().then(() => {
expect(wrapper.find(Form).props('version')).toBe(NEW_VERSION_FLAG);
expect(wrapper.find(Form).attributes('version')).toBe(NEW_VERSION_FLAG);
});
});

View File

@ -1,18 +1,12 @@
import { GlFormTextarea, GlFormCheckbox, GlButton, GlToggle } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { uniqueId } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Api from '~/api';
import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdown.vue';
import Form from '~/feature_flags/components/form.vue';
import Strategy from '~/feature_flags/components/strategy.vue';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
INTERNAL_ID_PREFIX,
DEFAULT_PERCENT_ROLLOUT,
LEGACY_FLAG,
NEW_VERSION_FLAG,
} from '~/feature_flags/constants';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
import { featureFlag, userList, allUsersStrategy } from '../mock_data';
@ -29,15 +23,8 @@ describe('feature flag form', () => {
const requiredInjections = {
environmentsEndpoint: '/environments.json',
projectId: '1',
glFeatures: {
featureFlagPermissions: true,
featureFlagsNewVersion: true,
},
};
const findAddNewScopeRow = () => wrapper.findByTestId('add-new-scope');
const findGlToggle = () => wrapper.find(GlToggle);
const factory = (props = {}, provide = {}) => {
wrapper = extendedWrapper(
shallowMount(Form, {
@ -100,328 +87,6 @@ describe('feature flag form', () => {
it('should render description textarea', () => {
expect(wrapper.find('#feature-flag-description').exists()).toBe(true);
});
describe('scopes', () => {
it('should render scopes table', () => {
expect(wrapper.find('.js-scopes-table').exists()).toBe(true);
});
it('should render scopes table with a new row ', () => {
expect(findAddNewScopeRow().exists()).toBe(true);
});
describe('status toggle', () => {
describe('without filled text input', () => {
it('should add a new scope with the text value empty and the status', () => {
findGlToggle().vm.$emit('change', true);
expect(wrapper.vm.formScopes).toHaveLength(1);
expect(wrapper.vm.formScopes[0].active).toEqual(true);
expect(wrapper.vm.formScopes[0].environmentScope).toEqual('');
expect(wrapper.vm.newScope).toEqual('');
});
});
it('has label', () => {
expect(findGlToggle().props('label')).toBe(Form.i18n.statusLabel);
});
it('should be disabled if the feature flag is not active', (done) => {
wrapper.setProps({ active: false });
wrapper.vm.$nextTick(() => {
expect(findGlToggle().props('disabled')).toBe(true);
done();
});
});
});
});
});
describe('with provided data', () => {
beforeEach(() => {
factory({
...requiredProps,
name: featureFlag.name,
description: featureFlag.description,
active: true,
version: LEGACY_FLAG,
scopes: [
{
id: 1,
active: true,
environmentScope: 'scope',
canUpdate: true,
protected: false,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '54',
rolloutUserIds: '123',
shouldIncludeUserIds: true,
},
{
id: 2,
active: true,
environmentScope: 'scope',
canUpdate: false,
protected: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '54',
rolloutUserIds: '123',
shouldIncludeUserIds: true,
},
],
});
});
describe('scopes', () => {
it('should be possible to remove a scope', () => {
expect(wrapper.findByTestId('feature-flag-delete').exists()).toEqual(true);
});
it('renders empty row to add a new scope', () => {
expect(findAddNewScopeRow().exists()).toEqual(true);
});
it('renders the user id checkbox', () => {
expect(wrapper.find(GlFormCheckbox).exists()).toBe(true);
});
it('renders the user id text area', () => {
expect(wrapper.find(GlFormTextarea).exists()).toBe(true);
expect(wrapper.find(GlFormTextarea).vm.value).toBe('123');
});
describe('update scope', () => {
describe('on click on toggle', () => {
it('should update the scope', () => {
findGlToggle().vm.$emit('change', false);
expect(wrapper.vm.formScopes[0].active).toBe(false);
});
it('should be disabled if the feature flag is not active', (done) => {
wrapper.setProps({ active: false });
wrapper.vm.$nextTick(() => {
expect(findGlToggle().props('disabled')).toBe(true);
done();
});
});
});
describe('on strategy change', () => {
it('should not include user IDs if All Users is selected', () => {
const scope = wrapper.find({ ref: 'scopeRow' });
scope.find('select').setValue(ROLLOUT_STRATEGY_ALL_USERS);
return wrapper.vm.$nextTick().then(() => {
expect(scope.find('#rollout-user-id-0').exists()).toBe(false);
});
});
});
});
describe('deleting an existing scope', () => {
beforeEach(() => {
wrapper.find('.js-delete-scope').vm.$emit('click');
});
it('should add `shouldBeDestroyed` key the clicked scope', () => {
expect(wrapper.vm.formScopes[0].shouldBeDestroyed).toBe(true);
});
it('should not render deleted scopes', () => {
expect(wrapper.vm.filteredScopes).toEqual([expect.objectContaining({ id: 2 })]);
});
});
describe('deleting a new scope', () => {
it('should remove the scope from formScopes', () => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environmentScope: 'new_scope',
active: false,
id: uniqueId(INTERNAL_ID_PREFIX),
canUpdate: true,
protected: false,
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
},
],
},
],
});
wrapper.find('.js-delete-scope').vm.$emit('click');
expect(wrapper.vm.formScopes).toEqual([]);
});
});
describe('with * scope', () => {
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environmentScope: '*',
active: false,
canUpdate: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
},
],
});
});
it('renders read-only name', () => {
expect(wrapper.find('.js-scope-all').exists()).toEqual(true);
});
});
describe('without permission to update', () => {
it('should have the flag name input disabled', () => {
const input = wrapper.find('#feature-flag-name');
expect(input.element.disabled).toBe(true);
});
it('should have the flag discription text area disabled', () => {
const textarea = wrapper.find('#feature-flag-description');
expect(textarea.element.disabled).toBe(true);
});
it('should have the scope that cannot be updated be disabled', () => {
const row = wrapper.findAll('.gl-responsive-table-row').at(2);
expect(row.find(EnvironmentsDropdown).vm.disabled).toBe(true);
expect(row.find(GlToggle).props('disabled')).toBe(true);
expect(row.find('.js-delete-scope').exists()).toBe(false);
});
});
});
describe('on submit', () => {
const selectFirstRolloutStrategyOption = (dropdownIndex) => {
wrapper
.findAll('select.js-rollout-strategy')
.at(dropdownIndex)
.findAll('option')
.at(1)
.setSelected();
};
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
active: true,
description: 'this is a feature flag',
scopes: [
{
id: 1,
environmentScope: 'production',
canUpdate: true,
protected: true,
active: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
},
],
});
return wrapper.vm.$nextTick();
});
it('should emit handleSubmit with the updated data', () => {
wrapper.find('#feature-flag-name').setValue('feature_flag_2');
return wrapper.vm
.$nextTick()
.then(() => {
wrapper
.find('.js-new-scope-name')
.find(EnvironmentsDropdown)
.vm.$emit('selectEnvironment', 'review');
return wrapper.vm.$nextTick();
})
.then(() => {
findAddNewScopeRow().find(GlToggle).vm.$emit('change', true);
})
.then(() => {
findGlToggle().vm.$emit('change', true);
return wrapper.vm.$nextTick();
})
.then(() => {
selectFirstRolloutStrategyOption(0);
return wrapper.vm.$nextTick();
})
.then(() => {
selectFirstRolloutStrategyOption(2);
return wrapper.vm.$nextTick();
})
.then(() => {
wrapper.find('.js-rollout-percentage').setValue('55');
return wrapper.vm.$nextTick();
})
.then(() => {
wrapper.find({ ref: 'submitButton' }).vm.$emit('click');
const data = wrapper.emitted().handleSubmit[0][0];
expect(data.name).toEqual('feature_flag_2');
expect(data.description).toEqual('this is a feature flag');
expect(data.active).toBe(true);
expect(data.scopes).toEqual([
{
id: 1,
active: true,
environmentScope: 'production',
canUpdate: true,
protected: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '55',
rolloutUserIds: '',
shouldIncludeUserIds: false,
},
{
id: expect.any(String),
active: false,
environmentScope: 'review',
canUpdate: true,
protected: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
},
{
id: expect.any(String),
active: true,
environmentScope: '',
canUpdate: true,
protected: false,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
shouldIncludeUserIds: false,
},
]);
});
});
});
});
describe('with strategies', () => {
@ -432,7 +97,6 @@ describe('feature flag form', () => {
name: featureFlag.name,
description: featureFlag.description,
active: true,
version: NEW_VERSION_FLAG,
strategies: [
{
type: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,

View File

@ -81,8 +81,6 @@ describe('New feature flag form', () => {
rolloutUserIds: '',
};
expect(wrapper.vm.scopes).toEqual([defaultScope]);
expect(wrapper.find(Form).props('scopes')).toContainEqual(defaultScope);
});
it('has an all users strategy by default', () => {

View File

@ -12,10 +12,8 @@ RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, :repository, group: group) }
let_it_be(:group_wiki) { create(:group_wiki, user: user) }
let_it_be(:project_wiki) { create(:project_wiki, user: user) }
let(:group_wiki_page) { create(:wiki_page, wiki: group_wiki) }
let(:project_wiki_page) { create(:wiki_page, wiki: project_wiki) }
fixture_subdir = 'api/markdown'
@ -28,7 +26,6 @@ RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
end
before do
stub_group_wikis(true)
sign_in(user)
end
@ -55,8 +52,6 @@ RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
"/groups/#{group.full_path}/preview_markdown"
when 'project_wiki'
"/#{project.full_path}/-/wikis/#{project_wiki_page.slug}/preview_markdown"
when 'group_wiki'
"/groups/#{group.full_path}/-/wikis/#{group_wiki_page.slug}/preview_markdown"
else
api "/markdown"
end

View File

@ -20,9 +20,6 @@
- name: attachment_link
context: project
markdown: '[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)'
- name: attachment_link
context: group_wiki
markdown: '[test-file](test-file.zip)'
- name: attachment_link
context: group
markdown: '[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)'

View File

@ -137,7 +137,7 @@ describe('Group Settings App', () => {
href: PACKAGES_DOCS_PATH,
target: '_blank',
});
expect(findLink().text()).toBe('More Information');
expect(findLink().text()).toBe('Learn more.');
});
it('calls the graphql API with the proper variables', () => {

View File

@ -15,14 +15,15 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
let_it_be(:sha) { nil }
subject(:response) do
resolve(described_class,
args: { project_path: project.full_path, content: content },
args: { project_path: project.full_path, content: content, sha: sha },
ctx: { current_user: user })
end
context 'with a valid .gitlab-ci.yml' do
shared_examples 'a valid config file' do
let(:fake_result) do
::Gitlab::Ci::Lint::Result.new(
merged_yaml: content,
@ -37,9 +38,22 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end
it 'lints the ci config file and returns the merged yaml file' do
expect(response[:merged_yaml]).to eq(content)
expect(response[:status]).to eq(:valid)
expect(response[:merged_yaml]).to eq(content)
expect(response[:errors]).to be_empty
expect(::Gitlab::Ci::Lint).to have_received(:new).with(current_user: user, project: project, sha: sha)
end
end
context 'with a valid .gitlab-ci.yml' do
context 'with a sha' do
let(:sha) { '1231231' }
it_behaves_like 'a valid config file'
end
context 'without a sha' do
it_behaves_like 'a valid config file'
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ServicesHelper do
RSpec.describe IntegrationsHelper do
describe '#integration_form_data' do
let(:fields) do
[

View File

@ -869,6 +869,71 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
describe '#blobs' do
let_it_be(:commit_oid) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
shared_examples 'a blob enumeration' do
it 'enumerates blobs' do
blobs = repository.blobs(revisions).to_a
expect(blobs.size).to eq(expected_blobs)
blobs.each do |blob|
expect(blob.data).to be_empty
expect(blob.id.size).to be(40)
end
end
end
context 'single revision' do
let(:revisions) { [commit_oid] }
let(:expected_blobs) { 53 }
it_behaves_like 'a blob enumeration'
end
context 'multiple revisions' do
let(:revisions) { ["^#{commit_oid}~", commit_oid] }
let(:expected_blobs) { 1 }
it_behaves_like 'a blob enumeration'
end
context 'pseudo revisions' do
let(:revisions) { ['master', '--not', '--all'] }
let(:expected_blobs) { 0 }
it_behaves_like 'a blob enumeration'
end
context 'blank revisions' do
let(:revisions) { [::Gitlab::Git::BLANK_SHA] }
let(:expected_blobs) { 0 }
before do
expect_any_instance_of(Gitlab::GitalyClient::BlobService)
.not_to receive(:list_blobs)
end
it_behaves_like 'a blob enumeration'
end
context 'partially blank revisions' do
let(:revisions) { [::Gitlab::Git::BLANK_SHA, commit_oid] }
let(:expected_blobs) { 53 }
before do
expect_next_instance_of(Gitlab::GitalyClient::BlobService) do |service|
expect(service)
.to receive(:list_blobs)
.with([commit_oid], kind_of(Hash))
.and_call_original
end
end
it_behaves_like 'a blob enumeration'
end
end
describe '#count_commits_between' do
subject { repository.count_commits_between('feature', 'master') }

View File

@ -384,11 +384,12 @@ RSpec.describe Gitlab::GitAccessSnippet do
it_behaves_like 'a push to repository to make it over the limit'
end
context 'when GIT_OBJECT_DIRECTORY_RELATIVE env var is not set' do
shared_examples_for 'a change with GIT_OBJECT_DIRECTORY_RELATIVE env var unset' do
let(:change_size) { 200 }
before do
allow(snippet.repository).to receive(:new_blobs).and_return(
stub_feature_flags(git_access_batched_changes_size: batched)
allow(snippet.repository).to receive(expected_call).and_return(
[double(:blob, size: change_size)]
)
end
@ -397,6 +398,20 @@ RSpec.describe Gitlab::GitAccessSnippet do
it_behaves_like 'a push to repository below the limit'
it_behaves_like 'a push to repository to make it over the limit'
end
context 'when batched computation is enabled' do
let(:batched) { true }
let(:expected_call) { :blobs }
it_behaves_like 'a change with GIT_OBJECT_DIRECTORY_RELATIVE env var unset'
end
context 'when batched computation is disabled' do
let(:batched) { false }
let(:expected_call) { :new_blobs }
it_behaves_like 'a change with GIT_OBJECT_DIRECTORY_RELATIVE env var unset'
end
end
describe 'HEAD realignment' do

View File

@ -88,4 +88,104 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
subject
end
end
describe '#list_blobs' do
let(:limit) { 0 }
let(:bytes_limit) { 0 }
let(:expected_params) { { revisions: revisions, limit: limit, bytes_limit: bytes_limit } }
before do
::Gitlab::GitalyClient.clear_stubs!
end
subject { client.list_blobs(revisions, limit: limit, bytes_limit: bytes_limit) }
context 'with a single revision' do
let(:revisions) { ['master'] }
it 'sends a list_blobs message' do
expect_next_instance_of(Gitaly::BlobService::Stub) do |service|
expect(service)
.to receive(:list_blobs)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
end
subject
end
end
context 'with multiple revisions' do
let(:revisions) { ['master', '--not', '--all'] }
it 'sends a list_blobs message' do
expect_next_instance_of(Gitaly::BlobService::Stub) do |service|
expect(service)
.to receive(:list_blobs)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
end
subject
end
end
context 'with multiple revisions and limits' do
let(:revisions) { ['master', '--not', '--all'] }
let(:limit) { 10 }
let(:bytes_lmit) { 1024 }
it 'sends a list_blobs message' do
expect_next_instance_of(Gitaly::BlobService::Stub) do |service|
expect(service)
.to receive(:list_blobs)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
end
subject
end
end
context 'with split contents' do
let(:revisions) { ['master'] }
it 'sends a list_blobs message', :aggregate_failures do
expect_next_instance_of(Gitaly::BlobService::Stub) do |service|
expect(service)
.to receive(:list_blobs)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([
Gitaly::ListBlobsResponse.new(blobs: [
Gitaly::ListBlobsResponse::Blob.new(oid: "012345", size: 8, data: "0x01"),
Gitaly::ListBlobsResponse::Blob.new(data: "23")
]),
Gitaly::ListBlobsResponse.new(blobs: [
Gitaly::ListBlobsResponse::Blob.new(data: "45"),
Gitaly::ListBlobsResponse::Blob.new(oid: "56", size: 4, data: "0x5"),
Gitaly::ListBlobsResponse::Blob.new(data: "6")
]),
Gitaly::ListBlobsResponse.new(blobs: [
Gitaly::ListBlobsResponse::Blob.new(oid: "78", size: 4, data: "0x78")
])
])
end
blobs = subject.to_a
expect(blobs.size).to be(3)
expect(blobs[0].id).to eq('012345')
expect(blobs[0].size).to eq(8)
expect(blobs[0].data).to eq('0x012345')
expect(blobs[1].id).to eq('56')
expect(blobs[1].size).to eq(4)
expect(blobs[1].data).to eq('0x56')
expect(blobs[2].id).to eq('78')
expect(blobs[2].size).to eq(4)
expect(blobs[2].data).to eq('0x78')
end
end
end
end

View File

@ -0,0 +1,61 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ScheduleLatestPipelineIdPopulation do
let(:namespaces) { table(:namespaces) }
let(:pipelines) { table(:ci_pipelines) }
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
let(:vulnerability_statistics) { table(:vulnerability_statistics) }
let(:letter_grade_a) { 0 }
let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
let(:project_1) { projects.create!(namespace_id: namespace.id, name: 'Foo 1') }
let(:project_2) { projects.create!(namespace_id: namespace.id, name: 'Foo 2') }
let(:project_3) { projects.create!(namespace_id: namespace.id, name: 'Foo 3') }
let(:project_4) { projects.create!(namespace_id: namespace.id, name: 'Foo 4') }
before do
project_settings.create!(project_id: project_1.id, has_vulnerabilities: true)
project_settings.create!(project_id: project_2.id, has_vulnerabilities: true)
project_settings.create!(project_id: project_3.id)
project_settings.create!(project_id: project_4.id, has_vulnerabilities: true)
pipeline = pipelines.create!(project_id: project_2.id, ref: 'master', sha: 'adf43c3a')
vulnerability_statistics.create!(project_id: project_2.id, letter_grade: letter_grade_a, latest_pipeline_id: pipeline.id)
vulnerability_statistics.create!(project_id: project_4.id, letter_grade: letter_grade_a)
allow(Gitlab).to receive(:ee?).and_return(is_ee?)
stub_const("#{described_class.name}::BATCH_SIZE", 1)
end
around do |example|
freeze_time { example.run }
end
context 'when the installation is FOSS' do
let(:is_ee?) { false }
it 'does not schedule any background job' do
migrate!
expect(BackgroundMigrationWorker.jobs.size).to be(0)
end
end
context 'when the installation is EE' do
let(:is_ee?) { true }
it 'schedules the background jobs' do
migrate!
expect(BackgroundMigrationWorker.jobs.size).to be(2)
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(described_class::DELAY_INTERVAL, project_1.id, project_1.id)
expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2 * described_class::DELAY_INTERVAL, project_4.id, project_4.id)
end
end
end

View File

@ -2768,6 +2768,41 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries)
end
end
context 'when the first try cannot get an exclusive lock' do
let(:retries) { 1 }
subject(:cancel_running) { pipeline.cancel_running(retries: retries) }
before do
build = create(:ci_build, :running, pipeline: pipeline)
allow(pipeline.cancelable_statuses).to receive(:find_in_batches).and_yield([build])
call_count = 0
allow(build).to receive(:cancel).and_wrap_original do |original, *args|
call_count >= retries ? raise(ActiveRecord::StaleObjectError) : original.call(*args)
call_count += 1
end
end
it 'retries again and cancels the build' do
cancel_running
expect(latest_status).to contain_exactly('canceled')
end
context 'when the retries parameter is 0' do
let(:retries) { 0 }
it 'raises error' do
expect do
cancel_running
end.to raise_error(ActiveRecord::StaleObjectError)
end
end
end
end
describe '#retry_failed' do

View File

@ -1190,8 +1190,6 @@ RSpec.describe API::MergeRequests do
expect(json_response['work_in_progress']).to be false
expect(json_response['merge_when_pipeline_succeeds']).to be_falsy
expect(json_response['merge_status']).to eq('can_be_merged')
expect(json_response['should_close_merge_request']).to be_falsy
expect(json_response['force_close_merge_request']).to be_falsy
expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size)
expect(json_response['merge_error']).to eq(merge_request.merge_error)
expect(json_response['user']['can_merge']).to be_truthy

View File

@ -338,4 +338,26 @@ RSpec.describe API::Services do
expect(response).to have_gitlab_http_status(:ok)
end
end
describe 'Pipelines Email Integration' do
let(:service_name) { 'pipelines-email' }
context 'notify_only_broken_pipelines property was saved as a string' do
before do
project.create_pipelines_email_integration(
active: false,
properties: {
"notify_only_broken_pipelines": "true",
"branches_to_be_notified": "default"
}
)
end
it 'returns boolean values for notify_only_broken_pipelines' do
get api("/projects/#{project.id}/services/#{service_name}", user)
expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
end
end
end
end

View File

@ -1,11 +0,0 @@
# frozen_string_literal: true
require_relative './after_next_helpers'
module ServicesHelper
include AfterNextHelpers
def expect_execution_of(service_class, *args)
expect_next(service_class, *args).to receive(:execute)
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
# named as `get_executed` to avoid clashing
# with `be_executed === have_attributes(executed: true)`
RSpec::Matchers.define :get_executed do |args = []|
include AfterNextHelpers
match do |service_class|
expect_next(service_class, *args).to receive(:execute)
end
end

View File

@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe ::JiraConnect::SyncBuildsWorker do
include AfterNextHelpers
include ServicesHelper
describe '#perform' do
let_it_be(:pipeline) { create(:ci_pipeline) }

View File

@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe ::JiraConnect::SyncDeploymentsWorker do
include AfterNextHelpers
include ServicesHelper
describe '#perform' do
let_it_be(:deployment) { create(:deployment) }

View File

@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe ::JiraConnect::SyncFeatureFlagsWorker do
include AfterNextHelpers
include ServicesHelper
describe '#perform' do
let_it_be(:feature_flag) { create(:operations_feature_flag) }

View File

@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe PostReceive do
include AfterNextHelpers
include ServicesHelper
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
@ -234,7 +233,7 @@ RSpec.describe PostReceive do
end
it 'calls Git::ProcessRefChangesService' do
expect_execution_of(Git::ProcessRefChangesService)
expect(Git::ProcessRefChangesService).to get_executed
perform
end
@ -269,7 +268,7 @@ RSpec.describe PostReceive do
allow(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
# silence hooks so we can isolate
allow_next(Key).to receive(:post_create_hook).and_return(true)
expect_execution_of(Git::ProcessRefChangesService)
expect(Git::ProcessRefChangesService).to get_executed
end
it 'calls SystemHooksService' do