Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b4e7d9d839
commit
8a37720edf
|
@ -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'
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class Admin::IntegrationsController < Admin::ApplicationController
|
||||
include IntegrationsActions
|
||||
include ServicesHelper
|
||||
include IntegrationsHelper
|
||||
|
||||
before_action :not_found, unless: -> { instance_level_integrations? }
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module OperationsHelper
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include IntegrationsHelper
|
||||
|
||||
def prometheus_integration
|
||||
strong_memoize(:prometheus_integration) do
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
6c617b919e6e0cba0bd62cc0d5056dcad3ebe1a9ce25102a288de5456cbaa6c3
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|-------------------------------|------------|---------|
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)'
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ServicesHelper do
|
||||
RSpec.describe IntegrationsHelper do
|
||||
describe '#integration_form_data' do
|
||||
let(:fields) do
|
||||
[
|
|
@ -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') }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue