Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-07 12:09:12 +00:00
parent 419f9c0ac3
commit cf37ae7acd
128 changed files with 6334 additions and 289 deletions

View File

@ -154,8 +154,6 @@ Performance/Count:
- 'app/helpers/groups_helper.rb'
- 'app/services/merge_requests/add_context_service.rb'
- 'ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb'
- 'ee/spec/controllers/projects/feature_flags_controller_spec.rb'
- 'ee/spec/requests/api/feature_flags_spec.rb'
- 'lib/gitlab/sidekiq_status.rb'
- 'spec/lib/gitlab/conflict/file_spec.rb'
- 'spec/lib/gitlab/git/tree_spec.rb'
@ -167,7 +165,6 @@ Performance/Count:
Performance/Detect:
Exclude:
- 'ee/spec/controllers/projects/dependencies_controller_spec.rb'
- 'ee/spec/controllers/projects/feature_flags_controller_spec.rb'
- 'spec/lib/gitlab/git/tree_spec.rb'
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
- 'spec/models/event_spec.rb'

View File

@ -3,9 +3,10 @@ import commitPipelinesTable from './pipelines_table.vue';
/**
* Used in:
* - Commit details View > Pipelines Tab > Pipelines Table.
* - Merge Request details View > Pipelines Tab > Pipelines Table.
* - New Merge Request View > Pipelines Tab > Pipelines Table.
* - Project Pipelines List (projects:pipelines:index)
* - Commit details View > Pipelines Tab > Pipelines Table (projects:commit:pipelines)
* - Merge Request details View > Pipelines Tab > Pipelines Table (projects:merge_requests:show)
* - New Merge Request View > Pipelines Tab > Pipelines Table (projects:merge_requests:creations:new)
*/
const CommitPipelinesTable = Vue.extend(commitPipelinesTable);

View File

@ -193,7 +193,7 @@ export default {
"
/>
<div v-else-if="shouldRenderTable" class="table-holder">
<div v-else-if="shouldRenderTable">
<gl-button
v-if="canRenderPipelineButton"
block

View File

@ -1,6 +1,6 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { groupBy } from 'lodash';
import { differenceBy } from 'lodash';
import produce from 'immer';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { extractCurrentDiscussion, extractDesign, extractDesigns } from './design_management_utils';
@ -132,10 +132,13 @@ const addNewDesignToStore = (store, designManagementUpload, query) => {
const data = produce(sourceData, draftData => {
const currentDesigns = extractDesigns(draftData);
const existingDesigns = groupBy(currentDesigns, 'filename');
const newDesigns = currentDesigns.concat(
designManagementUpload.designs.filter(d => !existingDesigns[d.filename]),
);
const difference = differenceBy(designManagementUpload.designs, currentDesigns, 'filename');
const newDesigns = currentDesigns
.map(design => {
return designManagementUpload.designs[design.filename] || design;
})
.concat(difference);
let newVersionNode;
const findNewVersions = designManagementUpload.designs.find(design => design.versions);

View File

@ -0,0 +1,192 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See https://github.com/microsoft/monaco-languages/blob/master/LICENSE.md
*--------------------------------------------------------------------------------------------*/
/* eslint-disable no-useless-escape */
/* eslint-disable @gitlab/require-i18n-strings */
const conf = {
comments: {
lineComment: '//',
blockComment: ['/*', '*/'],
},
brackets: [['{', '}'], ['[', ']'], ['(', ')']],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"', notIn: ['string'] },
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
],
};
const language = {
defaultToken: '',
tokenPostfix: '.hcl',
keywords: [
'var',
'local',
'path',
'for_each',
'any',
'string',
'number',
'bool',
'true',
'false',
'null',
'if ',
'else ',
'endif ',
'for ',
'in',
'endfor',
],
operators: [
'=',
'>=',
'<=',
'==',
'!=',
'+',
'-',
'*',
'/',
'%',
'&&',
'||',
'!',
'<',
'>',
'?',
'...',
':',
],
symbols: /[=><!~?:&|+\-*\/\^%]+/,
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
terraformFunctions: /(abs|ceil|floor|log|max|min|pow|signum|chomp|format|formatlist|indent|join|lower|regex|regexall|replace|split|strrev|substr|title|trimspace|upper|chunklist|coalesce|coalescelist|compact|concat|contains|distinct|element|flatten|index|keys|length|list|lookup|map|matchkeys|merge|range|reverse|setintersection|setproduct|setunion|slice|sort|transpose|values|zipmap|base64decode|base64encode|base64gzip|csvdecode|jsondecode|jsonencode|urlencode|yamldecode|yamlencode|abspath|dirname|pathexpand|basename|file|fileexists|fileset|filebase64|templatefile|formatdate|timeadd|timestamp|base64sha256|base64sha512|bcrypt|filebase64sha256|filebase64sha512|filemd5|filemd1|filesha256|filesha512|md5|rsadecrypt|sha1|sha256|sha512|uuid|uuidv5|cidrhost|cidrnetmask|cidrsubnet|tobool|tolist|tomap|tonumber|toset|tostring)/,
terraformMainBlocks: /(module|data|terraform|resource|provider|variable|output|locals)/,
tokenizer: {
root: [
// highlight main blocks
[
/^@terraformMainBlocks([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,
['type', '', 'string', '', 'string', '', '@brackets'],
],
// highlight all the remaining blocks
[
/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,
['identifier', '', 'string', '', 'string', '', '@brackets'],
],
// highlight block
[
/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)(=)(\{)/,
['identifier', '', 'string', '', 'operator', '', '@brackets'],
],
// terraform general highlight - shared with expressions
{ include: '@terraform' },
],
terraform: [
// highlight terraform functions
[/@terraformFunctions(\()/, ['type', '@brackets']],
// all other words are variables or keywords
[
/[a-zA-Z_]\w*-*/, // must work with variables such as foo-bar and also with negative numbers
{
cases: {
'@keywords': { token: 'keyword.$0' },
'@default': 'variable',
},
},
],
{ include: '@whitespace' },
{ include: '@heredoc' },
// delimiters and operators
[/[{}()\[\]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[
/@symbols/,
{
cases: {
'@operators': 'operator',
'@default': '',
},
},
],
// numbers
[/\d*\d+[eE]([\-+]?\d+)?/, 'number.float'],
[/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
[/\d[\d']*/, 'number'],
[/\d/, 'number'],
[/[;,.]/, 'delimiter'], // delimiter: after number because of .\d floats
// strings
[/"/, 'string', '@string'], // this will include expressions
[/'/, 'invalid'],
],
heredoc: [
[
/<<[-]*\s*["]?([\w\-]+)["]?/,
{ token: 'string.heredoc.delimiter', next: '@heredocBody.$1' },
],
],
heredocBody: [
[
/^([\w\-]+)$/,
{
cases: {
'$1==$S2': [
{
token: 'string.heredoc.delimiter',
next: '@popall',
},
],
'@default': 'string.heredoc',
},
},
],
[/./, 'string.heredoc'],
],
whitespace: [
[/[ \t\r\n]+/, ''],
[/\/\*/, 'comment', '@comment'],
[/\/\/.*$/, 'comment'],
[/#.*$/, 'comment'],
],
comment: [[/[^\/*]+/, 'comment'], [/\*\//, 'comment', '@pop'], [/[\/*]/, 'comment']],
string: [
[/\$\{/, { token: 'delimiter', next: '@stringExpression' }],
[/[^\\"\$]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, 'string', '@popall'],
],
stringInsideExpression: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, 'string', '@pop'],
],
stringExpression: [
[/\}/, { token: 'delimiter', next: '@pop' }],
[/"/, 'string', '@stringInsideExpression'],
{ include: '@terraform' },
],
},
};
export default {
id: 'hcl',
extensions: ['.tf', '.tfvars', '.hcl'],
aliases: ['Terraform', 'tf', 'HCL', 'hcl'],
conf,
language,
};

View File

@ -1,5 +1,6 @@
import vue from './vue';
import hcl from './hcl';
const languages = [vue];
const languages = [vue, hcl];
export default languages;

View File

@ -55,7 +55,7 @@ export default {
<div class="discussion-with-resolve-btn clearfix">
<reply-placeholder
data-qa-selector="discussion_reply_tab"
:button-text="s__('MergeRequests|Reply')"
:button-text="s__('MergeRequests|Reply...')"
@onClick="$emit('showReplyForm')"
/>

View File

@ -1,11 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
export default {
name: 'ReplyPlaceholder',
components: {
GlButton,
},
props: {
buttonText: {
type: String,
@ -16,13 +11,13 @@ export default {
</script>
<template>
<gl-button
category="primary"
variant="success"
class="js-vue-discussion-reply"
<button
ref="button"
type="button"
class="js-vue-discussion-reply btn btn-text-field"
:title="s__('MergeRequests|Add a reply')"
@click="$emit('onClick')"
>
{{ buttonText }}
</gl-button>
</button>
</template>

View File

@ -2,7 +2,6 @@
import { GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
import { generateConanRecipe } from '../utils';
import { PackageType } from '../../shared/constants';
export default {
@ -25,9 +24,6 @@ export default {
},
},
computed: {
conanRecipe() {
return generateConanRecipe(this.packageEntity);
},
showMetadata() {
const visibilityConditions = {
[PackageType.NUGET]: this.packageEntity.nuget_metadatum,
@ -73,7 +69,7 @@ export default {
data-testid="conan-recipe"
>
<gl-sprintf :message="$options.i18n.recipeText">
<template #recipe>{{ conanRecipe }}</template>
<template #recipe>{{ packageEntity.name }}</template>
</gl-sprintf>
</details-row>

View File

@ -1,4 +1,3 @@
import { generateConanRecipe } from '../utils';
import { PackageType } from '../../shared/constants';
import { getPackageTypeLabel } from '../../shared/utils';
import { NpmManager } from '../constants';
@ -20,10 +19,8 @@ export const packageIcon = ({ packageEntity }) => {
};
export const conanInstallationCommand = ({ packageEntity }) => {
const recipe = generateConanRecipe(packageEntity);
// eslint-disable-next-line @gitlab/require-i18n-strings
return `conan install ${recipe} --remote=gitlab`;
return `conan install ${packageEntity.name} --remote=gitlab`;
};
export const conanSetupCommand = ({ conanPath }) =>

View File

@ -8,16 +8,3 @@ export const trackInstallationTabChange = {
},
},
};
export function generateConanRecipe(packageEntity = {}) {
const {
name = '',
version = '',
conan_metadatum: {
package_username: packageUsername = '',
package_channel: packageChannel = '',
} = {},
} = packageEntity;
return `${name}/${version}@${packageUsername}/${packageChannel}`;
}

View File

@ -1,7 +1,3 @@
import initPackageList from '~/packages/list/packages_list_app_bundle';
document.addEventListener('DOMContentLoaded', () => {
if (document.getElementById('js-vue-packages-list')) {
initPackageList();
}
});
initPackageList();

View File

@ -321,7 +321,11 @@ export default {
</div>
</div>
<pipelines-timeago :duration="pipelineDuration" :finished-time="pipelineFinishedAt" />
<pipelines-timeago
class="gl-text-right"
:duration="pipelineDuration"
:finished-time="pipelineFinishedAt"
/>
<div
v-if="displayPipelineActions"

View File

@ -50,7 +50,7 @@ export default {
};
</script>
<template>
<div class="table-section section-15 pipelines-time-ago">
<div class="table-section section-15">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div>
<div class="table-mobile-content">
<p v-if="hasDuration" class="duration">

View File

@ -244,15 +244,20 @@
}
&.btn-text-field {
color: $gray-500;
justify-content: start;
width: 100%;
text-align: left;
padding: 6px 16px;
border-color: $border-color;
color: $gray-darkest;
background-color: $white;
&:hover,
&:active,
&:focus {
cursor: text;
box-shadow: none;
border-color: lighten($blue-300, 20%);
color: $gray-darkest;
}
}

View File

@ -0,0 +1,66 @@
@import 'mixins_and_variables_and_functions';
/**
* Pipelines Bundle
*
* Styles of pipeline lists
*
* Should affect pipelines table components rendered by:
* app/assets/javascripts/commit/pipelines/pipelines_bundle.js
*/
.pipelines {
.badge {
margin-bottom: 3px;
}
.pipeline-actions {
min-width: 170px; //Guarantees buttons don't break in several lines.
.btn-default {
color: $gl-text-color-secondary;
}
.btn.btn-retry:hover,
.btn.btn-retry:focus {
border-color: $dropdown-toggle-active-border-color;
background-color: $white-normal;
}
svg path {
fill: $gl-text-color-secondary;
}
.dropdown-menu {
max-height: $dropdown-max-height;
overflow-y: auto;
}
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
.fa {
color: $gl-text-color-secondary;
font-size: 14px;
}
}
.btn-group.open .btn-default {
background-color: $white-normal;
border-color: $border-white-normal;
}
.btn .text-center {
display: inline;
}
.tooltip {
white-space: nowrap;
}
}
.pipeline-tags .label-container {
white-space: normal;
}
}

View File

@ -1,92 +1,3 @@
.pipelines {
.stage {
max-width: 90px;
width: 90px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table-holder {
overflow: unset;
width: 100%;
}
.commit-title {
margin: 0;
white-space: normal;
@include media-breakpoint-down(sm) {
justify-content: flex-end;
}
}
.ci-table {
.badge {
margin-bottom: 3px;
}
.pipeline-id {
color: $black;
}
.pipelines-time-ago {
text-align: right;
}
.pipeline-actions {
min-width: 170px; //Guarantees buttons don't break in several lines.
.btn-default {
color: $gl-text-color-secondary;
}
.btn.btn-retry:hover,
.btn.btn-retry:focus {
border-color: $dropdown-toggle-active-border-color;
background-color: $white-normal;
}
svg path {
fill: $gl-text-color-secondary;
}
.dropdown-menu {
max-height: $dropdown-max-height;
overflow-y: auto;
}
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
.fa {
color: $gl-text-color-secondary;
font-size: 14px;
}
}
.btn-group.open .btn-default {
background-color: $white-normal;
border-color: $border-white-normal;
}
.btn .text-center {
display: inline;
}
.tooltip {
white-space: nowrap;
}
}
.pipeline-tags .label-container {
white-space: normal;
}
}
}
@include media-breakpoint-down(md) {
.content-list {
&.builds-content-list {
@ -246,11 +157,6 @@
}
}
// Pipeline visualization
.pipeline-actions {
border-bottom: 0;
}
.ci-build-text,
.ci-status-text {
font-weight: 200;

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class Projects::FeatureFlagsClientsController < Projects::ApplicationController
before_action :authorize_admin_feature_flags_client!
before_action :feature_flags_client
def reset_token
feature_flags_client.reset_token!
respond_to do |format|
format.json do
render json: feature_flags_client_token_json, status: :ok
end
end
end
private
def feature_flags_client
project.operations_feature_flags_client || not_found
end
def feature_flags_client_token_json
FeatureFlagsClientSerializer.new
.represent_token(feature_flags_client)
end
end

View File

@ -0,0 +1,172 @@
# frozen_string_literal: true
class Projects::FeatureFlagsController < Projects::ApplicationController
respond_to :html
before_action :authorize_read_feature_flag!
before_action :authorize_create_feature_flag!, only: [:new, :create]
before_action :authorize_update_feature_flag!, only: [:edit, :update]
before_action :authorize_destroy_feature_flag!, only: [:destroy]
before_action :feature_flag, only: [:edit, :update, :destroy]
before_action :ensure_legacy_flags_writable!, only: [:update]
before_action do
push_frontend_feature_flag(:feature_flag_permissions)
push_frontend_feature_flag(:feature_flags_new_version, project, default_enabled: true)
push_frontend_feature_flag(:feature_flags_legacy_read_only, project, default_enabled: true)
push_frontend_feature_flag(:feature_flags_legacy_read_only_override, project)
end
def index
@feature_flags = FeatureFlagsFinder
.new(project, current_user, scope: params[:scope])
.execute
.page(params[:page])
.per(30)
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: { feature_flags: feature_flags_json }.merge(summary_json)
end
end
end
def new
end
def show
respond_to do |format|
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render_success_json(feature_flag)
end
end
end
def create
result = FeatureFlags::CreateService.new(project, current_user, create_params).execute
if result[:status] == :success
respond_to do |format|
format.json { render_success_json(result[:feature_flag]) }
end
else
respond_to do |format|
format.json { render_error_json(result[:message]) }
end
end
end
def edit
end
def update
result = FeatureFlags::UpdateService.new(project, current_user, update_params).execute(feature_flag)
if result[:status] == :success
respond_to do |format|
format.json { render_success_json(result[:feature_flag]) }
end
else
respond_to do |format|
format.json { render_error_json(result[:message]) }
end
end
end
def destroy
result = FeatureFlags::DestroyService.new(project, current_user).execute(feature_flag)
if result[:status] == :success
respond_to do |format|
format.html { redirect_to_index(notice: _('Feature flag was successfully removed.')) }
format.json { render_success_json(feature_flag) }
end
else
respond_to do |format|
format.html { redirect_to_index(alert: _('Feature flag was not removed.')) }
format.json { render_error_json(result[:message]) }
end
end
end
protected
def feature_flag
@feature_flag ||= @noteable = if new_version_feature_flags_enabled?
project.operations_feature_flags.find_by_iid!(params[:iid])
else
project.operations_feature_flags.legacy_flag.find_by_iid!(params[:iid])
end
end
def new_version_feature_flags_enabled?
::Feature.enabled?(:feature_flags_new_version, project, default_enabled: true)
end
def ensure_legacy_flags_writable!
if ::Feature.enabled?(:feature_flags_legacy_read_only, project, default_enabled: true) &&
::Feature.disabled?(:feature_flags_legacy_read_only_override, project) &&
feature_flag.legacy_flag?
render_error_json(['Legacy feature flags are read-only'])
end
end
def create_params
params.require(:operations_feature_flag)
.permit(:name, :description, :active, :version,
scopes_attributes: [:environment_scope, :active,
strategies: [:name, parameters: [:groupId, :percentage, :userIds]]],
strategies_attributes: [:name, :user_list_id,
parameters: [:groupId, :percentage, :userIds, :rollout, :stickiness],
scopes_attributes: [:environment_scope]])
end
def update_params
params.require(:operations_feature_flag)
.permit(:name, :description, :active,
scopes_attributes: [:id, :environment_scope, :active, :_destroy,
strategies: [:name, parameters: [:groupId, :percentage, :userIds]]],
strategies_attributes: [:id, :name, :user_list_id, :_destroy,
parameters: [:groupId, :percentage, :userIds, :rollout, :stickiness],
scopes_attributes: [:id, :environment_scope, :_destroy]])
end
def feature_flag_json(feature_flag)
FeatureFlagSerializer
.new(project: @project, current_user: @current_user)
.represent(feature_flag)
end
def feature_flags_json
FeatureFlagSerializer
.new(project: @project, current_user: @current_user)
.with_pagination(request, response)
.represent(@feature_flags)
end
def summary_json
FeatureFlagSummarySerializer
.new(project: @project, current_user: @current_user)
.represent(@project)
end
def redirect_to_index(**args)
redirect_to project_feature_flags_path(@project), status: :found, **args
end
def render_success_json(feature_flag)
render json: feature_flag_json(feature_flag), status: :ok
end
def render_error_json(messages)
render json: { message: messages },
status: :bad_request
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class Projects::FeatureFlagsUserListsController < Projects::ApplicationController
before_action :authorize_admin_feature_flags_user_lists!
before_action :user_list, only: [:edit, :show]
def new
end
def edit
end
def show
end
private
def user_list
@user_list = project.operations_feature_flags_user_lists.find_by_iid!(params[:iid])
end
end

View File

@ -9,6 +9,7 @@ module UserCalloutsHelper
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
WEBHOOKS_MOVED = 'webhooks_moved'
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version'
def show_admin_integrations_moved?
!user_dismissed?(ADMIN_INTEGRATIONS_MOVED)
@ -50,6 +51,10 @@ module UserCalloutsHelper
customize_homepage && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
end
def show_feature_flags_new_version?
!user_dismissed?(FEATURE_FLAGS_NEW_VERSION)
end
private
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)

View File

@ -9,5 +9,15 @@ module BlobViewer
self.extensions = Gitlab::MarkupHelper::EXTENSIONS
self.file_types = %i(readme)
self.binary = false
def banzai_render_context
{}.tap do |h|
h[:rendered] = blob.rendered_markup if blob.respond_to?(:rendered_markup)
if Feature.enabled?(:cached_markdown_blob, blob.project)
h[:cache_key] = ['blob', blob.id, 'commit', blob.commit_id]
end
end
end
end
end

View File

@ -8,10 +8,13 @@ module Packages
end
def detail_view
name = @package.name
name = @package.conan_recipe if @package.conan?
package_detail = {
id: @package.id,
created_at: @package.created_at,
name: @package.name,
name: name,
package_files: @package.package_files.map { |pf| build_package_file_view(pf) },
package_type: @package.package_type,
project_id: @package.project_id,
@ -20,6 +23,7 @@ module Packages
version: @package.version
}
package_detail[:conan_package_name] = @package.name if @package.conan?
package_detail[:maven_metadatum] = @package.maven_metadatum if @package.maven_metadatum
package_detail[:nuget_metadatum] = @package.nuget_metadatum if @package.nuget_metadatum
package_detail[:composer_metadatum] = @package.composer_metadatum if @package.composer_metadatum

View File

@ -33,7 +33,7 @@ module Ci
pipeline_params.fetch(:target_revision))
downstream_pipeline = service.execute(
pipeline_params.fetch(:source), pipeline_params[:execute_params]) do |pipeline|
pipeline_params.fetch(:source), **pipeline_params[:execute_params]) do |pipeline|
pipeline.variables.build(@bridge.downstream_variables)
end

View File

@ -70,7 +70,7 @@ module Ci
push_options: params[:push_options] || {},
chat_data: params[:chat_data],
bridge: bridge,
**extra_options(options))
**extra_options(**options))
# Ensure we never persist the pipeline when dry_run: true
@pipeline.readonly! if command.dry_run?

View File

@ -13,8 +13,8 @@ module NotificationRecipients
NotificationRecipient.new(user, *args).notifiable?
end
def self.build_recipients(*args)
::NotificationRecipients::Builder::Default.new(*args).notification_recipients
def self.build_recipients(target, current_user, **args)
::NotificationRecipients::Builder::Default.new(target, current_user, **args).notification_recipients
end
def self.build_new_note_recipients(*args)
@ -25,8 +25,8 @@ module NotificationRecipients
::NotificationRecipients::Builder::MergeRequestUnmergeable.new(*args).notification_recipients
end
def self.build_project_maintainers_recipients(*args)
::NotificationRecipients::Builder::ProjectMaintainers.new(*args).notification_recipients
def self.build_project_maintainers_recipients(target, **args)
::NotificationRecipients::Builder::ProjectMaintainers.new(target, **args).notification_recipients
end
def self.build_new_release_recipients(*args)

View File

@ -1,4 +1,3 @@
- blob = viewer.blob
- context = blob.respond_to?(:rendered_markup) ? { rendered: blob.rendered_markup } : {}
.file-content.md
= markup(blob.name, blob.data, context)
= markup(blob.name, blob.data, viewer.banzai_render_context)

View File

@ -1,4 +1,5 @@
- page_title _('Pipelines'), "#{@commit.title} (#{@commit.short_id})", _('Commits')
- add_page_specific_style 'page_bundles/pipelines'
= render 'commit_box'
= render 'ci_menu'

View File

@ -9,7 +9,7 @@
feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments', anchor: 'scoping-environments-with-specs'),

View File

@ -7,7 +7,7 @@
feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments', anchor: 'scoping-environments-with-specs'),

View File

@ -1,6 +1,7 @@
- add_to_breadcrumbs _("Merge Requests"), project_merge_requests_path(@project)
- breadcrumb_title _("New")
- page_title _("New Merge Request")
- add_page_specific_style 'page_bundles/pipelines'
- if @merge_request.can_be_created && !params[:change_branches]
= render 'new_submit'

View File

@ -8,6 +8,7 @@
- suggest_changes_help_path = help_page_path('user/discussions/index.md', anchor: 'suggest-changes')
- number_of_pipelines = @pipelines.size
- mr_action = j(params[:tab].presence || 'show')
- add_page_specific_style 'page_bundles/pipelines'
.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
= render "projects/merge_requests/mr_title"

View File

@ -1,4 +1,5 @@
- page_title _('Pipelines')
- add_page_specific_style 'page_bundles/pipelines'
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"

View File

@ -0,0 +1,5 @@
---
title: Remove type column on audit_events table
merge_request: 43703
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Restore snippet repositories from backups
merge_request: 43696
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Display conan recipe as package name on package detail page
merge_request: 44294
author:
type: changed

View File

@ -1,6 +0,0 @@
---
title: Replacing deprecated Bootstrap button with GlButton and updating btn-text-field
class to align with styles
merge_request: 41430
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Update Design thumbnail after uploading an image with the same filename
merge_request: 44305
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Move before_script into script for CQ template
merge_request: 42782
author: Vicken Simonian @vicken.papaya
type: fixed

View File

@ -0,0 +1,5 @@
---
title: IDE editor - Adding syntax highlighting for terraform / hcl
merge_request: 44056
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Introduce required_code_owners_sections table
merge_request: 43573
author:
type: added

View File

@ -182,6 +182,7 @@ module Gitlab
config.assets.precompile << "page_bundles/merge_conflicts.css"
config.assets.precompile << "page_bundles/milestone.css"
config.assets.precompile << "page_bundles/pipeline.css"
config.assets.precompile << "page_bundles/pipelines.css"
config.assets.precompile << "page_bundles/todos.css"
config.assets.precompile << "page_bundles/xterm.css"
config.assets.precompile << "lazy_bundles/cropper.css"

View File

@ -0,0 +1,7 @@
---
name: cached_markdown_blob
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44300
rollout_issue_url:
type: development
group: group::source code
default_enabled: false

View File

@ -1,7 +1,7 @@
---
name: ci_dynamic_child_pipeline
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23790
rollout_issue_url:
group: group::continuous integration
type: development
default_enabled: true

View File

@ -1,7 +1,7 @@
---
name: ci_lint_creates_pipeline_with_dry_run
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37828
rollout_issue_url:
group: group::continuous integration
type: development
default_enabled: true

View File

@ -1,7 +1,7 @@
---
name: ci_raise_job_rules_without_workflow_rules_warning
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38387
rollout_issue_url:
group: group::continuous integration
type: development
default_enabled: true

View File

@ -1,7 +1,7 @@
---
name: ci_store_pipeline_messages
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url:
rollout_issue_url:
group: group::continuous integration
type: development
default_enabled: true

View File

@ -1,7 +1,7 @@
---
name: ci_yaml_limit_size
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url:
rollout_issue_url:
group: group::continuous integration
type: development
default_enabled: true

View File

@ -1,7 +1,7 @@
---
name: efficient_counter_attribute
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35878
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/238535
group: group::continuous integration
type: development
default_enabled: false

View File

@ -0,0 +1,7 @@
---
name: feature_flag_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18198
rollout_issue_url:
group: group::progressive delivery
type: development
default_enabled: false

View File

@ -0,0 +1,7 @@
---
name: feature_flag_permissions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/10096
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/254981
group: group::progressive delivery
type: development
default_enabled: false

View File

@ -0,0 +1,7 @@
---
name: feature_flags_legacy_read_only
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/240985
group: group::progressive delivery
type: development
default_enabled: true

View File

@ -0,0 +1,7 @@
---
name: feature_flags_legacy_read_only_override
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40431
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/240985
group: group::progressive delivery
type: development
default_enabled: false

View File

@ -373,9 +373,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
resources :feature_flags, param: :iid do
resources :feature_flag_issues, only: [:index, :create, :destroy], as: 'issues', path: 'issues'
end
resources :feature_flags, param: :iid
resource :feature_flags_client, only: [] do
post :reset_token
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
class CreateRequiredCodeOwnersSections < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
with_lock_retries do
create_table :required_code_owners_sections, if_not_exists: true do |t|
t.references :protected_branch, null: false, foreign_key: { on_delete: :cascade }
t.text :name, null: false
end
end
add_text_limit :required_code_owners_sections, :name, 1024
end
def down
with_lock_retries do
drop_table :required_code_owners_sections, if_exists: true
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddIndexOnVulnerabilitiesStateCase < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_vulnerabilities_on_state_case_id'
STATE_ORDER_ARRAY_POSITION = 'ARRAY_POSITION(ARRAY[1, 4, 3, 2]::smallint[], state)'
disable_ddl_transaction!
def up
add_concurrent_index :vulnerabilities, "#{STATE_ORDER_ARRAY_POSITION}, id DESC", name: INDEX_NAME
add_concurrent_index :vulnerabilities, "#{STATE_ORDER_ARRAY_POSITION} DESC, id DESC", name: "#{INDEX_NAME}_desc"
end
def down
remove_concurrent_index_by_name :vulnerabilities, "#{INDEX_NAME}_desc"
remove_concurrent_index_by_name :vulnerabilities, INDEX_NAME
end
end

View File

@ -0,0 +1,125 @@
# frozen_string_literal: true
class RemoveTypeFromAuditEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::SchemaHelpers
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
SOURCE_TABLE_NAME = 'audit_events'
PARTITIONED_TABLE_NAME = 'audit_events_part_5fc467ac26'
TRIGGER_FUNCTION_NAME = 'table_sync_function_2be879775d'
def up
with_lock_retries do
remove_column SOURCE_TABLE_NAME, :type
create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
<<~SQL
IF (TG_OP = 'DELETE') THEN
DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE #{PARTITIONED_TABLE_NAME}
SET author_id = NEW.author_id,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
target_type = NEW.target_type,
target_id = NEW.target_id,
created_at = NEW.created_at
WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
author_id,
entity_id,
entity_type,
details,
ip_address,
author_name,
entity_path,
target_details,
target_type,
target_id,
created_at)
VALUES (NEW.id,
NEW.author_id,
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
NEW.target_details,
NEW.target_type,
NEW.target_id,
NEW.created_at);
END IF;
RETURN NULL;
SQL
end
remove_column PARTITIONED_TABLE_NAME, :type
end
end
def down
with_lock_retries do
add_column SOURCE_TABLE_NAME, :type, :string
add_column PARTITIONED_TABLE_NAME, :type, :string
create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
<<~SQL
IF (TG_OP = 'DELETE') THEN
DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE #{PARTITIONED_TABLE_NAME}
SET author_id = NEW.author_id,
type = NEW.type,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
target_type = NEW.target_type,
target_id = NEW.target_id,
created_at = NEW.created_at
WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
author_id,
type,
entity_id,
entity_type,
details,
ip_address,
author_name,
entity_path,
target_details,
target_type,
target_id,
created_at)
VALUES (NEW.id,
NEW.author_id,
NEW.type,
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
NEW.target_details,
NEW.target_type,
NEW.target_id,
NEW.created_at);
END IF;
RETURN NULL;
SQL
end
end
end
end

View File

@ -0,0 +1 @@
106757b0f30d3c89fcafa13be92271090fa107831fd538ee087d7ce212842492

View File

@ -0,0 +1 @@
346d0e913212d6e84528d47228ba7e6d0cf4a396e7fc921f7c684acfaaeeedb8

View File

@ -0,0 +1 @@
260f392c3ff257960dc7b198473056e7bf9b9a668403d2f05391d2b7989cf83c

View File

@ -19,7 +19,6 @@ IF (TG_OP = 'DELETE') THEN
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE audit_events_part_5fc467ac26
SET author_id = NEW.author_id,
type = NEW.type,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
@ -34,7 +33,6 @@ ELSIF (TG_OP = 'UPDATE') THEN
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO audit_events_part_5fc467ac26 (id,
author_id,
type,
entity_id,
entity_type,
details,
@ -47,7 +45,6 @@ ELSIF (TG_OP = 'INSERT') THEN
created_at)
VALUES (NEW.id,
NEW.author_id,
NEW.type,
NEW.entity_id,
NEW.entity_type,
NEW.details,
@ -69,7 +66,6 @@ COMMENT ON FUNCTION table_sync_function_2be879775d() IS 'Partitioning migration:
CREATE TABLE audit_events_part_5fc467ac26 (
id bigint NOT NULL,
author_id integer NOT NULL,
type character varying,
entity_id integer NOT NULL,
entity_type character varying NOT NULL,
details text,
@ -9541,7 +9537,6 @@ ALTER SEQUENCE atlassian_identities_user_id_seq OWNED BY atlassian_identities.us
CREATE TABLE audit_events (
id integer NOT NULL,
author_id integer NOT NULL,
type character varying,
entity_id integer NOT NULL,
entity_type character varying NOT NULL,
details text,
@ -15451,6 +15446,22 @@ CREATE TABLE repository_languages (
share double precision NOT NULL
);
CREATE TABLE required_code_owners_sections (
id bigint NOT NULL,
protected_branch_id bigint NOT NULL,
name text NOT NULL,
CONSTRAINT check_e58d53741e CHECK ((char_length(name) <= 1024))
);
CREATE SEQUENCE required_code_owners_sections_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE required_code_owners_sections_id_seq OWNED BY required_code_owners_sections.id;
CREATE TABLE requirements (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -17749,6 +17760,8 @@ ALTER TABLE ONLY releases ALTER COLUMN id SET DEFAULT nextval('releases_id_seq':
ALTER TABLE ONLY remote_mirrors ALTER COLUMN id SET DEFAULT nextval('remote_mirrors_id_seq'::regclass);
ALTER TABLE ONLY required_code_owners_sections ALTER COLUMN id SET DEFAULT nextval('required_code_owners_sections_id_seq'::regclass);
ALTER TABLE ONLY requirements ALTER COLUMN id SET DEFAULT nextval('requirements_id_seq'::regclass);
ALTER TABLE ONLY requirements_management_test_reports ALTER COLUMN id SET DEFAULT nextval('requirements_management_test_reports_id_seq'::regclass);
@ -19030,6 +19043,9 @@ ALTER TABLE ONLY releases
ALTER TABLE ONLY remote_mirrors
ADD CONSTRAINT remote_mirrors_pkey PRIMARY KEY (id);
ALTER TABLE ONLY required_code_owners_sections
ADD CONSTRAINT required_code_owners_sections_pkey PRIMARY KEY (id);
ALTER TABLE ONLY requirements_management_test_reports
ADD CONSTRAINT requirements_management_test_reports_pkey PRIMARY KEY (id);
@ -21207,6 +21223,8 @@ CREATE INDEX index_remote_mirrors_on_project_id ON remote_mirrors USING btree (p
CREATE UNIQUE INDEX index_repository_languages_on_project_and_languages_id ON repository_languages USING btree (project_id, programming_language_id);
CREATE INDEX index_required_code_owners_sections_on_protected_branch_id ON required_code_owners_sections USING btree (protected_branch_id);
CREATE INDEX index_requirements_management_test_reports_on_author_id ON requirements_management_test_reports USING btree (author_id);
CREATE INDEX index_requirements_management_test_reports_on_build_id ON requirements_management_test_reports USING btree (build_id);
@ -21603,6 +21621,10 @@ CREATE INDEX index_vulnerabilities_on_resolved_by_id ON vulnerabilities USING bt
CREATE INDEX index_vulnerabilities_on_start_date_sourcing_milestone_id ON vulnerabilities USING btree (start_date_sourcing_milestone_id);
CREATE INDEX index_vulnerabilities_on_state_case_id ON vulnerabilities USING btree (array_position(ARRAY[(1)::smallint, (4)::smallint, (3)::smallint, (2)::smallint], state), id DESC);
CREATE INDEX index_vulnerabilities_on_state_case_id_desc ON vulnerabilities USING btree (array_position(ARRAY[(1)::smallint, (4)::smallint, (3)::smallint, (2)::smallint], state) DESC, id DESC);
CREATE INDEX index_vulnerabilities_on_updated_by_id ON vulnerabilities USING btree (updated_by_id);
CREATE INDEX index_vulnerability_exports_on_author_id ON vulnerability_exports USING btree (author_id);
@ -23310,6 +23332,9 @@ ALTER TABLE ONLY clusters_kubernetes_namespaces
ALTER TABLE ONLY approval_merge_request_rules_users
ADD CONSTRAINT fk_rails_80e6801803 FOREIGN KEY (approval_merge_request_rule_id) REFERENCES approval_merge_request_rules(id) ON DELETE CASCADE;
ALTER TABLE ONLY required_code_owners_sections
ADD CONSTRAINT fk_rails_817708cf2d FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_site_profiles
ADD CONSTRAINT fk_rails_83e309d69e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;

View File

@ -142,7 +142,7 @@ In GitLab 13.2 and later versions, promoting a secondary node to a primary while
If you have already run the [preflight checks](planned_failover.md#preflight-checks) separately or don't want to run them, you can skip preflight checks with:
```shell
gitlab-ctl promote-to-primary-node --skip-preflight-check
gitlab-ctl promote-to-primary-node --skip-preflight-checks
```
You can also promote the secondary node to primary **without any further confirmation**, even when preflight checks fail:

View File

@ -171,9 +171,7 @@ Checking integrity of Uploads
Done!
```
To delete these references to remote uploads that were deleted externally, open the [GitLab Rails Console](../troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session)
and run:
[Rails Console](../troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session):
To delete these references to remote uploads that were deleted externally, open the [GitLab Rails Console](../troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session) and run:
```ruby
uploads_deleted=0

View File

@ -15888,6 +15888,11 @@ type Requirement {
"""
iid: ID!
"""
Indicates if latest test report was created by user
"""
lastTestReportManuallyCreated: Boolean
"""
Latest requirement test report state
"""
@ -20153,7 +20158,7 @@ type Vulnerability implements Noteable {
severity: VulnerabilitySeverity
"""
State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED)
State of the vulnerability (DETECTED, CONFIRMED, RESOLVED, DISMISSED)
"""
state: VulnerabilityState
@ -20815,6 +20820,16 @@ enum VulnerabilitySort {
"""
severity_desc
"""
State in ascending order
"""
state_asc
"""
State in descending order
"""
state_desc
"""
Title in ascending order
"""

View File

@ -45944,6 +45944,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lastTestReportManuallyCreated",
"description": "Indicates if latest test report was created by user",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lastTestReportState",
"description": "Latest requirement test report state",
@ -58489,7 +58503,7 @@
},
{
"name": "state",
"description": "State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED)",
"description": "State of the vulnerability (DETECTED, CONFIRMED, RESOLVED, DISMISSED)",
"args": [
],
@ -60468,6 +60482,18 @@
"description": "Report Type in ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "state_desc",
"description": "State in descending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "state_asc",
"description": "State in ascending order",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
@ -60487,7 +60513,7 @@
"deprecationReason": null
},
{
"name": "DISMISSED",
"name": "CONFIRMED",
"description": null,
"isDeprecated": false,
"deprecationReason": null
@ -60499,7 +60525,7 @@
"deprecationReason": null
},
{
"name": "CONFIRMED",
"name": "DISMISSED",
"description": null,
"isDeprecated": false,
"deprecationReason": null

View File

@ -2135,6 +2135,7 @@ Represents a requirement.
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `id` | ID! | ID of the requirement |
| `iid` | ID! | Internal ID of the requirement |
| `lastTestReportManuallyCreated` | Boolean | Indicates if latest test report was created by user |
| `lastTestReportState` | TestReportState | Latest requirement test report state |
| `project` | Project! | Project to which the requirement belongs |
| `state` | RequirementState! | State of the requirement |
@ -2820,7 +2821,7 @@ Represents a vulnerability.
| `resolvedOnDefaultBranch` | Boolean! | Indicates whether the vulnerability is fixed on the default branch or not |
| `scanner` | VulnerabilityScanner | Scanner metadata for the vulnerability. |
| `severity` | VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) |
| `state` | VulnerabilityState | State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED) |
| `state` | VulnerabilityState | State of the vulnerability (DETECTED, CONFIRMED, RESOLVED, DISMISSED) |
| `title` | String | Title of the vulnerability |
| `userNotesCount` | Int! | Number of user notes attached to the vulnerability |
| `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource |
@ -3766,6 +3767,8 @@ Vulnerability sort values.
| `report_type_desc` | Report Type in descending order |
| `severity_asc` | Severity in ascending order |
| `severity_desc` | Severity in descending order |
| `state_asc` | State in ascending order |
| `state_desc` | State in descending order |
| `title_asc` | Title in ascending order |
| `title_desc` | Title in descending order |

View File

@ -364,17 +364,7 @@ standard Rails migration helper methods. Calling more than one migration
helper is not a problem if they're executed on the same table.
Using the `with_lock_retries` helper method is advised when a database
migration involves one of the high-traffic tables:
- `users`
- `projects`
- `namespaces`
- `gitlab_subscriptions`
- `issues`
- `merge_requests`
- `ci_pipelines`
- `ci_builds`
- `notes`
migration involves one of the [high-traffic tables](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3).
Example changes:

View File

@ -29,16 +29,16 @@ to the desired destination:
cd <destination folder>
```
[Create a branch](create-branch.md) to add your file to, before it is added to the master
(main) branch of the project. It is not strictly necessary, but working directly in
the `master` branch is not recommended unless your project is very small, and you are
[Create a branch](create-branch.md) to add your file to, before it's added to the master
(main) branch of the project. It's not strictly necessary, but working directly in
the `master` branch is not recommended unless your project is very small, and you're
the only person working on it. You can [switch to an existing branch](start-using-git.md#work-on-an-existing-branch),
if you have one already.
if you've one already.
Using your standard tool for copying files (for example, Finder in macOS, or File Explorer
in Windows), put the file into a directory within the GitLab project.
Check if your file is actually present in the directory (if you are in Windows,
Check if your file is actually present in the directory (if you're in Windows,
use `dir` instead):
```shell
@ -79,7 +79,7 @@ Now you can push (send) your changes (in the branch `<branch-name>`) to GitLab
git push origin <branch-name>
```
Your image will be added to your branch in your repository in GitLab.
Your image is added to your branch in your repository in GitLab.
<!-- ## Troubleshooting

View File

@ -153,6 +153,9 @@ module API
mount ::API::Environments
mount ::API::ErrorTracking
mount ::API::Events
mount ::API::FeatureFlags
mount ::API::FeatureFlagScopes
mount ::API::FeatureFlagsUserLists
mount ::API::Features
mount ::API::Files
mount ::API::FreezePeriods

View File

@ -0,0 +1,158 @@
# frozen_string_literal: true
module API
class FeatureFlagScopes < Grape::API::Instance
include PaginationParams
ENVIRONMENT_SCOPE_ENDPOINT_REQUIREMENTS = FeatureFlags::FEATURE_FLAG_ENDPOINT_REQUIREMENTS
.merge(environment_scope: API::NO_SLASH_URL_PART_REGEX)
before do
authorize_read_feature_flags!
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
resource :feature_flag_scopes do
desc 'Get all effective feature flags under the environment' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::DetailedLegacyScope
end
params do
requires :environment, type: String, desc: 'The environment name'
end
get do
present scopes_for_environment, with: ::API::Entities::FeatureFlag::DetailedLegacyScope
end
end
params do
requires :name, type: String, desc: 'The name of the feature flag'
end
resource 'feature_flags/:name', requirements: FeatureFlags::FEATURE_FLAG_ENDPOINT_REQUIREMENTS do
resource :scopes do
desc 'Get all scopes of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
params do
use :pagination
end
get do
present paginate(feature_flag.scopes), with: ::API::Entities::FeatureFlag::LegacyScope
end
desc 'Create a scope of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
params do
requires :environment_scope, type: String, desc: 'The environment scope of the scope'
requires :active, type: Boolean, desc: 'Whether the scope is active'
requires :strategies, type: JSON, desc: 'The strategies of the scope'
end
post do
authorize_update_feature_flag!
result = ::FeatureFlags::UpdateService
.new(user_project, current_user, scopes_attributes: [declared_params])
.execute(feature_flag)
if result[:status] == :success
present scope, with: ::API::Entities::FeatureFlag::LegacyScope
else
render_api_error!(result[:message], result[:http_status])
end
end
params do
requires :environment_scope, type: String, desc: 'URL-encoded environment scope'
end
resource ':environment_scope', requirements: ENVIRONMENT_SCOPE_ENDPOINT_REQUIREMENTS do
desc 'Get a scope of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
get do
present scope, with: ::API::Entities::FeatureFlag::LegacyScope
end
desc 'Update a scope of a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
params do
optional :active, type: Boolean, desc: 'Whether the scope is active'
optional :strategies, type: JSON, desc: 'The strategies of the scope'
end
put do
authorize_update_feature_flag!
scope_attributes = declared_params.merge(id: scope.id)
result = ::FeatureFlags::UpdateService
.new(user_project, current_user, scopes_attributes: [scope_attributes])
.execute(feature_flag)
if result[:status] == :success
updated_scope = result[:feature_flag].scopes
.find { |scope| scope.environment_scope == params[:environment_scope] }
present updated_scope, with: ::API::Entities::FeatureFlag::LegacyScope
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Delete a scope from a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag::LegacyScope
end
delete do
authorize_update_feature_flag!
param = { scopes_attributes: [{ id: scope.id, _destroy: true }] }
result = ::FeatureFlags::UpdateService
.new(user_project, current_user, param)
.execute(feature_flag)
if result[:status] == :success
status :no_content
else
render_api_error!(result[:message], result[:http_status])
end
end
end
end
end
end
helpers do
def authorize_read_feature_flags!
authorize! :read_feature_flag, user_project
end
def authorize_update_feature_flag!
authorize! :update_feature_flag, feature_flag
end
def feature_flag
@feature_flag ||= user_project.operations_feature_flags
.find_by_name!(params[:name])
end
def scope
@scope ||= feature_flag.scopes
.find_by_environment_scope!(CGI.unescape(params[:environment_scope]))
end
def scopes_for_environment
Operations::FeatureFlagScope
.for_unleash_client(user_project, params[:environment])
end
end
end
end

266
lib/api/feature_flags.rb Normal file
View File

@ -0,0 +1,266 @@
# frozen_string_literal: true
module API
class FeatureFlags < Grape::API::Instance
include PaginationParams
FEATURE_FLAG_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
.merge(name: API::NO_SLASH_URL_PART_REGEX)
before do
authorize_read_feature_flags!
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
resource :feature_flags do
desc 'Get all feature flags of a project' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag
end
params do
optional :scope, type: String, desc: 'The scope of feature flags',
values: %w[enabled disabled]
use :pagination
end
get do
feature_flags = ::FeatureFlagsFinder
.new(user_project, current_user, declared_params(include_missing: false))
.execute
present_entity(paginate(feature_flags))
end
desc 'Create a new feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag
end
params do
requires :name, type: String, desc: 'The name of feature flag'
optional :description, type: String, desc: 'The description of the feature flag'
optional :active, type: Boolean, desc: 'Active/inactive value of the flag'
optional :version, type: String, desc: 'The version of the feature flag'
optional :scopes, type: Array do
requires :environment_scope, type: String, desc: 'The environment scope of the scope'
requires :active, type: Boolean, desc: 'Active/inactive of the scope'
requires :strategies, type: JSON, desc: 'The strategies of the scope'
end
optional :strategies, type: Array do
requires :name, type: String, desc: 'The strategy name'
requires :parameters, type: JSON, desc: 'The strategy parameters'
optional :scopes, type: Array do
requires :environment_scope, type: String, desc: 'The environment scope of the scope'
end
end
end
post do
authorize_create_feature_flag!
attrs = declared_params(include_missing: false)
ensure_post_version_2_flags_enabled! if attrs[:version] == 'new_version_flag'
rename_key(attrs, :scopes, :scopes_attributes)
rename_key(attrs, :strategies, :strategies_attributes)
update_value(attrs, :strategies_attributes) do |strategies|
strategies.map { |s| rename_key(s, :scopes, :scopes_attributes) }
end
result = ::FeatureFlags::CreateService
.new(user_project, current_user, attrs)
.execute
if result[:status] == :success
present_entity(result[:feature_flag])
else
render_api_error!(result[:message], result[:http_status])
end
end
end
params do
requires :feature_flag_name, type: String, desc: 'The name of the feature flag'
end
resource 'feature_flags/:feature_flag_name', requirements: FEATURE_FLAG_ENDPOINT_REQUIREMENTS do
desc 'Get a feature flag of a project' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag
end
get do
authorize_read_feature_flag!
present_entity(feature_flag)
end
desc 'Enable a strategy for a feature flag on an environment' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag
end
params do
requires :environment_scope, type: String, desc: 'The environment scope of the feature flag'
requires :strategy, type: JSON, desc: 'The strategy to be enabled on the scope'
end
post :enable do
not_found! unless Feature.enabled?(:feature_flag_api, user_project)
render_api_error!('Version 2 flags not supported', :unprocessable_entity) if new_version_flag_present?
result = ::FeatureFlags::EnableService
.new(user_project, current_user, params).execute
if result[:status] == :success
status :ok
present_entity(result[:feature_flag])
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Disable a strategy for a feature flag on an environment' do
detail 'This feature is going to be introduced in GitLab 12.5 if `feature_flag_api` feature flag is removed'
success ::API::Entities::FeatureFlag
end
params do
requires :environment_scope, type: String, desc: 'The environment scope of the feature flag'
requires :strategy, type: JSON, desc: 'The strategy to be disabled on the scope'
end
post :disable do
not_found! unless Feature.enabled?(:feature_flag_api, user_project)
render_api_error!('Version 2 flags not supported', :unprocessable_entity) if feature_flag.new_version_flag?
result = ::FeatureFlags::DisableService
.new(user_project, current_user, params).execute
if result[:status] == :success
status :ok
present_entity(result[:feature_flag])
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Update a feature flag' do
detail 'This feature will be introduced in GitLab 13.1 if feature_flags_new_version feature flag is removed'
success ::API::Entities::FeatureFlag
end
params do
optional :name, type: String, desc: 'The name of the feature flag'
optional :description, type: String, desc: 'The description of the feature flag'
optional :active, type: Boolean, desc: 'Active/inactive value of the flag'
optional :strategies, type: Array do
optional :id, type: Integer, desc: 'The strategy id'
optional :name, type: String, desc: 'The strategy type'
optional :parameters, type: JSON, desc: 'The strategy parameters'
optional :_destroy, type: Boolean, desc: 'Delete the strategy when true'
optional :scopes, type: Array do
optional :id, type: Integer, desc: 'The environment scope id'
optional :environment_scope, type: String, desc: 'The environment scope of the scope'
optional :_destroy, type: Boolean, desc: 'Delete the scope when true'
end
end
end
put do
not_found! unless feature_flags_new_version_enabled?
authorize_update_feature_flag!
render_api_error!('PUT operations are not supported for legacy feature flags', :unprocessable_entity) if feature_flag.legacy_flag?
attrs = declared_params(include_missing: false)
rename_key(attrs, :strategies, :strategies_attributes)
update_value(attrs, :strategies_attributes) do |strategies|
strategies.map { |s| rename_key(s, :scopes, :scopes_attributes) }
end
result = ::FeatureFlags::UpdateService
.new(user_project, current_user, attrs)
.execute(feature_flag)
if result[:status] == :success
present_entity(result[:feature_flag])
else
render_api_error!(result[:message], result[:http_status])
end
end
desc 'Delete a feature flag' do
detail 'This feature was introduced in GitLab 12.5'
success ::API::Entities::FeatureFlag
end
delete do
authorize_destroy_feature_flag!
result = ::FeatureFlags::DestroyService
.new(user_project, current_user, declared_params(include_missing: false))
.execute(feature_flag)
if result[:status] == :success
present_entity(result[:feature_flag])
else
render_api_error!(result[:message], result[:http_status])
end
end
end
end
helpers do
def authorize_read_feature_flags!
authorize! :read_feature_flag, user_project
end
def authorize_read_feature_flag!
authorize! :read_feature_flag, feature_flag
end
def authorize_create_feature_flag!
authorize! :create_feature_flag, user_project
end
def authorize_update_feature_flag!
authorize! :update_feature_flag, feature_flag
end
def authorize_destroy_feature_flag!
authorize! :destroy_feature_flag, feature_flag
end
def present_entity(result)
present result,
with: ::API::Entities::FeatureFlag,
feature_flags_new_version_enabled: feature_flags_new_version_enabled?
end
def ensure_post_version_2_flags_enabled!
unless feature_flags_new_version_enabled?
render_api_error!('Version 2 flags are not enabled for this project', :unprocessable_entity)
end
end
def feature_flag
@feature_flag ||= if feature_flags_new_version_enabled?
user_project.operations_feature_flags.find_by_name!(params[:feature_flag_name])
else
user_project.operations_feature_flags.legacy_flag.find_by_name!(params[:feature_flag_name])
end
end
def new_version_flag_present?
user_project.operations_feature_flags.new_version_flag.find_by_name(params[:name]).present?
end
def feature_flags_new_version_enabled?
Feature.enabled?(:feature_flags_new_version, user_project, default_enabled: true)
end
def rename_key(hash, old_key, new_key)
hash[new_key] = hash.delete(old_key) if hash.key?(old_key)
hash
end
def update_value(hash, key)
hash[key] = yield(hash[key]) if hash.key?(key)
hash
end
end
end
end

View File

@ -0,0 +1,100 @@
# frozen_string_literal: true
module API
class FeatureFlagsUserLists < Grape::API::Instance
include PaginationParams
error_formatter :json, -> (message, _backtrace, _options, _env, _original_exception) {
message.is_a?(String) ? { message: message }.to_json : message.to_json
}
before do
authorize_admin_feature_flags_user_lists!
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
resource :feature_flags_user_lists do
desc 'Get all feature flags user lists of a project' do
detail 'This feature was introduced in GitLab 12.10'
success ::API::Entities::FeatureFlag::UserList
end
params do
use :pagination
end
get do
present paginate(user_project.operations_feature_flags_user_lists),
with: ::API::Entities::FeatureFlag::UserList
end
desc 'Create a feature flags user list for a project' do
detail 'This feature was introduced in GitLab 12.10'
success ::API::Entities::FeatureFlag::UserList
end
params do
requires :name, type: String, desc: 'The name of the list'
requires :user_xids, type: String, desc: 'A comma separated list of external user ids'
end
post do
list = user_project.operations_feature_flags_user_lists.create(declared_params)
if list.save
present list, with: ::API::Entities::FeatureFlag::UserList
else
render_api_error!(list.errors.full_messages, :bad_request)
end
end
end
params do
requires :iid, type: String, desc: 'The internal id of the user list'
end
resource 'feature_flags_user_lists/:iid' do
desc 'Get a single feature flag user list belonging to a project' do
detail 'This feature was introduced in GitLab 12.10'
success ::API::Entities::FeatureFlag::UserList
end
get do
present user_project.operations_feature_flags_user_lists.find_by_iid!(params[:iid]),
with: ::API::Entities::FeatureFlag::UserList
end
desc 'Update a feature flag user list' do
detail 'This feature was introduced in GitLab 12.10'
success ::API::Entities::FeatureFlag::UserList
end
params do
optional :name, type: String, desc: 'The name of the list'
optional :user_xids, type: String, desc: 'A comma separated list of external user ids'
end
put do
list = user_project.operations_feature_flags_user_lists.find_by_iid!(params[:iid])
if list.update(declared_params(include_missing: false))
present list, with: ::API::Entities::FeatureFlag::UserList
else
render_api_error!(list.errors.full_messages, :bad_request)
end
end
desc 'Delete a feature flag user list' do
detail 'This feature was introduced in GitLab 12.10'
end
delete do
list = user_project.operations_feature_flags_user_lists.find_by_iid!(params[:iid])
unless list.destroy
render_api_error!(list.errors.full_messages, :conflict)
end
end
end
end
helpers do
def authorize_admin_feature_flags_user_lists!
authorize! :admin_feature_flags_user_lists, user_project
end
end
end
end

View File

@ -46,6 +46,10 @@ module Backup
restore_repository(project, Gitlab::GlRepository::DESIGN)
end
Snippet.find_each(batch_size: 1000) do |snippet|
restore_repository(snippet, Gitlab::GlRepository::SNIPPET)
end
restore_object_pools
end

View File

@ -9,9 +9,8 @@ code_quality:
DOCKER_TLS_CERTDIR: ""
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10-gitlab.1"
needs: []
before_script:
- export SOURCE_CODE=$PWD
script:
- export SOURCE_CODE=$PWD
- |
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then

View File

@ -95,7 +95,9 @@ module Gitlab
elsif ordering_by_similarity?(order_value)
['similarity', order_value.direction, order_value.expr]
elsif ordering_by_case?(order_value)
[order_value.expr.case.name.to_s, order_value.direction, order_value.expr]
['case_order_value', order_value.direction, order_value.expr]
elsif ordering_by_array_position?(order_value)
['array_position', order_value.direction, order_value.expr]
else
[order_value.expr.name, order_value.direction, nil]
end
@ -106,6 +108,11 @@ module Gitlab
order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'lower'
end
# determine if ordering using ARRAY_POSITION, eg. "ORDER BY ARRAY_POSITION(Array[4,3,1,2]::smallint, state)"
def ordering_by_array_position?(order_value)
order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'array_position'
end
# determine if ordering using SIMILARITY scoring based on Gitlab::Database::SimilarityScore
def ordering_by_similarity?(order_value)
Gitlab::Database::SimilarityScore.order_by_similarity?(order_value)

View File

@ -66,7 +66,7 @@ module GoogleApi
cluster_options = make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons)
request_body = Google::Apis::ContainerV1beta1::CreateClusterRequest.new(cluster_options)
request_body = Google::Apis::ContainerV1beta1::CreateClusterRequest.new(**cluster_options)
service.create_cluster(project_id, zone, request_body, options: user_agent_header)
end

View File

@ -8028,6 +8028,15 @@ msgstr ""
msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan."
msgstr ""
msgid "DastProfiles|AJAX spider"
msgstr ""
msgid "DastProfiles|Active"
msgstr ""
msgid "DastProfiles|Active scan will make active attacks against the target site while Passive scan will not"
msgstr ""
msgid "DastProfiles|Are you sure you want to delete this profile?"
msgstr ""
@ -8067,6 +8076,9 @@ msgstr ""
msgid "DastProfiles|Could not update the site profile. Please try again."
msgstr ""
msgid "DastProfiles|Debug messages"
msgstr ""
msgid "DastProfiles|Do you want to discard this scanner profile?"
msgstr ""
@ -8085,9 +8097,18 @@ msgstr ""
msgid "DastProfiles|Edit site profile"
msgstr ""
msgid "DastProfiles|Enable it to include the debug messages in DAST console output"
msgstr ""
msgid "DastProfiles|Enable it to run the AJAX spider (in addition to the traditional spider) to crawl the target site"
msgstr ""
msgid "DastProfiles|Error Details"
msgstr ""
msgid "DastProfiles|Hide debug messages"
msgstr ""
msgid "DastProfiles|Manage Profiles"
msgstr ""
@ -8139,6 +8160,9 @@ msgstr ""
msgid "DastProfiles|Scanner Profiles"
msgstr ""
msgid "DastProfiles|Show debug messages"
msgstr ""
msgid "DastProfiles|Site Profile"
msgstr ""
@ -8178,6 +8202,9 @@ msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr ""
msgid "DastProfiles|Turn on AJAX spider"
msgstr ""
msgid "DastProfiles|Validate"
msgstr ""
@ -16050,7 +16077,7 @@ msgstr ""
msgid "MergeRequests|Jump to next unresolved thread"
msgstr ""
msgid "MergeRequests|Reply"
msgid "MergeRequests|Reply..."
msgstr ""
msgid "MergeRequests|Resolve this thread in a new issue"
@ -17892,6 +17919,9 @@ msgstr ""
msgid "Omnibus Protected Paths throttle is active, and takes priority over these settings. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}."
msgstr ""
msgid "On"
msgstr ""
msgid "On track"
msgstr ""
@ -28716,7 +28746,13 @@ msgstr ""
msgid "Vulnerability|Comments"
msgstr ""
msgid "Vulnerability|Crash Address"
msgid "Vulnerability|Crash address"
msgstr ""
msgid "Vulnerability|Crash state"
msgstr ""
msgid "Vulnerability|Crash type"
msgstr ""
msgid "Vulnerability|Description"

View File

@ -21,7 +21,7 @@ module RuboCop
TABLE_METHODS = %i(create_table create_table_if_not_exists change_table).freeze
def high_traffic_tables
@high_traffic_tables ||= rubocop_migrations_config.dig('Migration/UpdateLargeTable', 'DeniedTables')
@high_traffic_tables ||= rubocop_migrations_config.dig('Migration/UpdateLargeTable', 'HighTrafficTables')
end
# Returns true if the given node originated from the db/migrate directory.

View File

@ -1,6 +1,7 @@
# Make sure to update the docs if this file moves. Docs URL: https://docs.gitlab.com/ce/development/migration_style_guide.html#when-to-use-the-helper-method
Migration/UpdateLargeTable:
Enabled: true
DeniedTables: &denied_tables # size in GB (>= 10 GB on GitLab.com as of 02/2020) and/or number of records
HighTrafficTables: &high_traffic_tables # size in GB (>= 10 GB on GitLab.com as of 02/2020) and/or number of records
- :audit_events
- :ci_build_trace_sections
- :ci_builds

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::FeatureFlagsClientsController do
include Gitlab::Routing
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
describe 'POST reset_token.json' do
subject(:reset_token) do
post :reset_token,
params: { namespace_id: project.namespace, project_id: project },
format: :json
end
before do
sign_in(user)
end
context 'when user is a project maintainer' do
before do
project.add_maintainer(user)
end
context 'and feature flags client exist' do
it 'regenerates feature flags client token' do
project.create_operations_feature_flags_client!
expect { reset_token }.to change { project.reload.feature_flags_client_token }
expect(json_response['token']).to eq(project.feature_flags_client_token)
end
end
context 'but feature flags client does not exist' do
it 'returns 404' do
reset_token
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when user is not a project maintainer' do
before do
project.add_developer(user)
end
it 'returns 404' do
reset_token
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,113 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::FeatureFlagsUserListsController do
let_it_be(:project) { create(:project) }
let_it_be(:reporter) { create(:user) }
let_it_be(:developer) { create(:user) }
before_all do
project.add_reporter(reporter)
project.add_developer(developer)
end
def request_params(extra_params = {})
{ namespace_id: project.namespace, project_id: project }.merge(extra_params)
end
describe 'GET #new' do
it 'redirects when the user is unauthenticated' do
get(:new, params: request_params)
expect(response).to redirect_to(new_user_session_path)
end
it 'returns not found if the user does not belong to the project' do
user = create(:user)
sign_in(user)
get(:new, params: request_params)
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns not found for a reporter' do
sign_in(reporter)
get(:new, params: request_params)
expect(response).to have_gitlab_http_status(:not_found)
end
it 'renders the new page for a developer' do
sign_in(developer)
get(:new, params: request_params)
expect(response).to have_gitlab_http_status(:ok)
end
end
describe 'GET #edit' do
before do
sign_in(developer)
end
it 'renders the edit page for a developer' do
list = create(:operations_feature_flag_user_list, project: project)
get(:edit, params: request_params(iid: list.iid))
expect(response).to have_gitlab_http_status(:ok)
end
it 'returns not found with an iid that does not exist' do
list = create(:operations_feature_flag_user_list, project: project)
get(:edit, params: request_params(iid: list.iid + 1))
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns not found for a list belonging to a another project' do
other_project = create(:project)
list = create(:operations_feature_flag_user_list, project: other_project)
get(:edit, params: request_params(iid: list.iid))
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe 'GET #show' do
before do
sign_in(developer)
end
it 'renders the page for a developer' do
list = create(:operations_feature_flag_user_list, project: project)
get(:show, params: request_params(iid: list.iid))
expect(response).to have_gitlab_http_status(:ok)
end
it 'returns not found with an iid that does not exist' do
list = create(:operations_feature_flag_user_list, project: project)
get(:show, params: request_params(iid: list.iid + 1))
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns not found for a list belonging to a another project' do
other_project = create(:project)
list = create(:operations_feature_flag_user_list, project: other_project)
get(:show, params: request_params(iid: list.iid))
expect(response).to have_gitlab_http_status(:not_found)
end
end
end

View File

@ -834,7 +834,7 @@ RSpec.describe 'GFM autocomplete', :js do
end
def start_and_cancel_discussion
click_button('Reply')
click_button('Reply...')
fill_in('note_note', with: 'Whoops!')

View File

@ -223,7 +223,7 @@ end
def write_reply_to_discussion(button_text: 'Start a review', text: 'Line is wrong', resolve: false, unresolve: false)
page.within(first('.diff-files-holder .discussion-reply-holder')) do
click_button('Reply')
click_button('Reply...')
fill_in('note_note', with: text)

View File

@ -186,7 +186,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
it 'adds as discussion' do
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false)
expect(page).to have_css('.notes_holder .note.note-discussion', count: 1)
expect(page).to have_button('Reply')
expect(page).to have_button('Reply...')
end
end
end

View File

@ -146,7 +146,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to comment' do
page.within '.diff-content' do
click_button 'Reply'
click_button 'Reply...'
find(".js-unresolve-checkbox").set false
find('.js-note-text').set 'testing'
@ -176,7 +176,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to comment & unresolve thread' do
page.within '.diff-content' do
click_button 'Reply'
click_button 'Reply...'
find('.js-note-text').set 'testing'
@ -205,7 +205,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to comment & resolve thread' do
page.within '.diff-content' do
click_button 'Reply'
click_button 'Reply...'
find('.js-note-text').set 'testing'
@ -438,7 +438,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to comment & resolve thread' do
page.within '.diff-content' do
click_button 'Reply'
click_button 'Reply...'
find('.js-note-text').set 'testing'
@ -457,7 +457,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
page.within '.diff-content' do
click_button 'Resolve thread'
click_button 'Reply'
click_button 'Reply...'
find('.js-note-text').set 'testing'

View File

@ -37,7 +37,7 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do
end
it 'does not render avatars after commenting on discussion tab' do
click_button 'Reply'
click_button 'Reply...'
page.within('.js-discussion-note-form') do
find('.note-textarea').native.send_keys('Test comment')
@ -132,7 +132,7 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do
end
it 'adds avatar when commenting' do
click_button 'Reply'
click_button 'Reply...'
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')
@ -151,7 +151,7 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do
it 'adds multiple comments' do
3.times do
click_button 'Reply'
click_button 'Reply...'
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')

View File

@ -60,7 +60,7 @@ RSpec.describe 'Merge request > User sees threads', :js do
it 'can be replied to' do
within(".discussion[data-discussion-id='#{discussion_id}']") do
click_button 'Reply'
click_button 'Reply...'
fill_in 'note[note]', with: 'Test!'
click_button 'Comment'

View File

@ -27,7 +27,7 @@ RSpec.describe 'Merge request > User sees notes from forked project', :js do
expect(page).to have_content('A commit comment')
page.within('.discussion-notes') do
find('.js-vue-discussion-reply').click
find('.btn-text-field').click
scroll_to(page.find('#note_note', visible: false))
find('#note_note').send_keys('A reply comment')
find('.js-comment-button').click

View File

@ -19,7 +19,7 @@ RSpec.describe 'project commit pipelines', :js do
context 'when no builds triggered yet' do
it 'shows the ID of the first pipeline' do
page.within('.table-holder') do
page.within('.pipelines .ci-table') do
expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
end
end

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User deletes feature flag user list', :js do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
before do
project.add_developer(developer)
sign_in(developer)
end
context 'with a list' do
before do
create(:operations_feature_flag_user_list, project: project, name: 'My List')
end
it 'deletes the list' do
visit(project_feature_flags_path(project, scope: 'userLists'))
delete_user_list_button.click
delete_user_list_modal_confirmation_button.click
expect(page).to have_text('Lists 0')
end
end
context 'with a list that is in use' do
before do
list = create(:operations_feature_flag_user_list, project: project, name: 'My List')
feature_flag = create(:operations_feature_flag, :new_version_flag, project: project)
create(:operations_strategy, feature_flag: feature_flag, name: 'gitlabUserList', user_list: list)
end
it 'does not delete the list' do
visit(project_feature_flags_path(project, scope: 'userLists'))
delete_user_list_button.click
delete_user_list_modal_confirmation_button.click
expect(page).to have_text('User list is associated with a strategy')
expect(page).to have_text('Lists 1')
expect(page).to have_text('My List')
alert_dismiss_button.click
expect(page).not_to have_text('User list is associated with a strategy')
end
end
def delete_user_list_button
find("button[data-testid='delete-user-list']")
end
def delete_user_list_modal_confirmation_button
find("button[data-testid='modal-confirm']")
end
def alert_dismiss_button
find("div[data-testid='serverErrors'] button")
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User edits feature flag user list', :js do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
before do
project.add_developer(developer)
sign_in(developer)
end
it 'prefills the edit form with the list name' do
list = create(:operations_feature_flag_user_list, project: project, name: 'My List Name')
visit(edit_project_feature_flags_user_list_path(project, list))
expect(page).to have_field 'Name', with: 'My List Name'
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User sees feature flag user list details', :js do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
before do
project.add_developer(developer)
sign_in(developer)
end
it 'displays the list name' do
list = create(:operations_feature_flag_user_list, project: project, name: 'My List')
visit(project_feature_flags_user_list_path(project, list))
expect(page).to have_text('My List')
end
end

View File

@ -0,0 +1,200 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User creates feature flag', :js do
include FeatureFlagHelpers
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
end
it 'user creates a flag enabled for user ids' do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('test_feature', 'Test feature')
within_strategy_row(1) do
select 'User IDs', from: 'Type'
fill_in 'User IDs', with: 'user1, user2'
environment_plus_button.click
environment_search_input.set('production')
environment_search_results.first.click
end
click_button 'Create feature flag'
expect_user_to_see_feature_flags_index_page
expect(page).to have_text('test_feature')
end
it 'user creates a flag with default environment scopes' do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('test_flag', 'Test flag')
within_strategy_row(1) do
select 'All users', from: 'Type'
end
click_button 'Create feature flag'
expect_user_to_see_feature_flags_index_page
expect(page).to have_text('test_flag')
edit_feature_flag_button.click
within_strategy_row(1) do
expect(page).to have_text('All users')
expect(page).to have_text('All environments')
end
end
it 'removes the correct strategy when a strategy is deleted' do
visit(new_project_feature_flag_path(project))
click_button 'Add strategy'
within_strategy_row(1) do
select 'All users', from: 'Type'
end
within_strategy_row(2) do
select 'Percent of users', from: 'Type'
end
within_strategy_row(1) do
delete_strategy_button.click
end
within_strategy_row(1) do
expect(page).to have_select('Type', selected: 'Percent of users')
end
end
context 'with new version flags disabled' do
before do
stub_feature_flags(feature_flags_new_version: false)
end
context 'when creates without changing scopes' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('ci_live_trace', 'For live trace')
click_button 'Create feature flag'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
end
end
end
end
context 'when creates with disabling the default scope' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('ci_live_trace', 'For live trace')
within_scope_row(1) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*')
end
end
end
end
context 'when creates with an additional scope' do
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('mr_train', '')
within_scope_row(2) do
within_environment_spec do
find('.js-env-search > input').set("review/*")
find('.js-create-button').click
end
end
within_scope_row(2) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(2)')).to have_content('review/*')
end
end
end
end
context 'when searches an environment name for scope creation' do
let!(:environment) { create(:environment, name: 'production', project: project) }
before do
visit(new_project_feature_flag_path(project))
set_feature_flag_info('mr_train', '')
within_scope_row(2) do
within_environment_spec do
find('.js-env-search > input').set('prod')
click_button 'production'
end
end
click_button 'Create feature flag'
end
it 'shows the created feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(2)')).to have_content('production')
end
end
end
end
end
private
def set_feature_flag_info(name, description)
fill_in 'Name', with: name
fill_in 'Description', with: description
end
def environment_plus_button
find('.js-new-environments-dropdown')
end
def environment_search_input
find('.js-new-environments-dropdown input')
end
def environment_search_results
all('.js-new-environments-dropdown button.dropdown-item')
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User deletes feature flag', :js do
include FeatureFlagHelpers
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let!(:feature_flag) do
create_flag(project, 'ci_live_trace', false,
description: 'For live trace feature')
end
before do
project.add_developer(user)
stub_feature_flags(feature_flag_permissions: false)
sign_in(user)
visit(project_feature_flags_path(project))
find('.js-feature-flag-delete-button').click
click_button('Delete feature flag')
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'user does not see feature flag' do
expect(page).to have_no_content('ci_live_trace')
end
end

View File

@ -0,0 +1,147 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User sees feature flag list', :js do
include FeatureFlagHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
before_all do
project.add_developer(user)
end
before do
sign_in(user)
end
context 'with legacy feature flags' do
before do
create_flag(project, 'ci_live_trace', false).tap do |feature_flag|
create_scope(feature_flag, 'review/*', true)
end
create_flag(project, 'drop_legacy_artifacts', false)
create_flag(project, 'mr_train', true).tap do |feature_flag|
create_scope(feature_flag, 'production', false)
end
stub_feature_flags(feature_flags_legacy_read_only_override: false)
end
it 'user sees the first flag' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect(page.find('.js-feature-flag-id')).to have_content('^1')
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_not_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*')
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(2)')).to have_content('review/*')
end
end
end
it 'user sees the second flag' do
visit(project_feature_flags_path(project))
within_feature_flag_row(2) do
expect(page.find('.js-feature-flag-id')).to have_content('^2')
expect(page.find('.feature-flag-name')).to have_content('drop_legacy_artifacts')
expect_status_toggle_button_not_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(1)')).to have_content('*')
end
end
end
it 'user sees the third flag' do
visit(project_feature_flags_path(project))
within_feature_flag_row(3) do
expect(page.find('.js-feature-flag-id')).to have_content('^3')
expect(page.find('.feature-flag-name')).to have_content('mr_train')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('[data-qa-selector="feature-flag-scope-info-badge"]:nth-child(1)')).to have_content('*')
expect(page.find('[data-qa-selector="feature-flag-scope-muted-badge"]:nth-child(2)')).to have_content('production')
end
end
end
it 'user sees the status toggle disabled' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect_status_toggle_button_to_be_disabled
end
end
context 'when legacy feature flags are not read-only' do
before do
stub_feature_flags(feature_flags_legacy_read_only: false)
end
it 'user updates the status toggle' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
status_toggle_button.click
expect_status_toggle_button_to_be_checked
end
end
end
context 'when legacy feature flags are read-only but the override is active for a project' do
before do
stub_feature_flags(
feature_flags_legacy_read_only: true,
feature_flags_legacy_read_only_override: project
)
end
it 'user updates the status toggle' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
status_toggle_button.click
expect_status_toggle_button_to_be_checked
end
end
end
end
context 'with new version flags' do
before do
create(:operations_feature_flag, :new_version_flag, project: project,
name: 'my_flag', active: false)
end
it 'user updates the status toggle' do
visit(project_feature_flags_path(project))
within_feature_flag_row(1) do
status_toggle_button.click
expect_status_toggle_button_to_be_checked
end
end
end
context 'when there are no feature flags' do
before do
visit(project_feature_flags_path(project))
end
it 'shows empty page' do
expect(page).to have_text 'Get started with feature flags'
expect(page).to have_selector('.btn-success', text: 'New feature flag')
expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure')
end
end
end

View File

@ -0,0 +1,195 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User updates feature flag', :js do
include FeatureFlagHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
before_all do
project.add_developer(user)
end
before do
stub_feature_flags(
feature_flag_permissions: false,
feature_flags_legacy_read_only_override: false
)
sign_in(user)
end
context 'with a new version feature flag' do
let!(:feature_flag) do
create_flag(project, 'test_flag', false, version: Operations::FeatureFlag.versions['new_version_flag'],
description: 'For testing')
end
let!(:strategy) do
create(:operations_strategy, feature_flag: feature_flag,
name: 'default', parameters: {})
end
let!(:scope) do
create(:operations_scope, strategy: strategy, environment_scope: '*')
end
it 'user adds a second strategy' do
visit(edit_project_feature_flag_path(project, feature_flag))
wait_for_requests
click_button 'Add strategy'
within_strategy_row(2) do
select 'Percent of users', from: 'Type'
fill_in 'Percentage', with: '15'
end
click_button 'Save changes'
edit_feature_flag_button.click
within_strategy_row(1) do
expect(page).to have_text 'All users'
expect(page).to have_text 'All environments'
end
within_strategy_row(2) do
expect(page).to have_text 'Percent of users'
expect(page).to have_field 'Percentage', with: '15'
expect(page).to have_text 'All environments'
end
end
it 'user toggles the flag on' do
visit(edit_project_feature_flag_path(project, feature_flag))
status_toggle_button.click
click_button 'Save changes'
within_feature_flag_row(1) do
expect_status_toggle_button_to_be_checked
end
end
end
context 'with a legacy feature flag' do
let!(:feature_flag) do
create_flag(project, 'ci_live_trace', true,
description: 'For live trace feature')
end
let!(:scope) { create_scope(feature_flag, 'review/*', true) }
context 'when legacy flags are editable' do
before do
stub_feature_flags(feature_flags_legacy_read_only: false)
visit(edit_project_feature_flag_path(project, feature_flag))
end
it 'user sees persisted default scope' do
within_scope_row(1) do
within_environment_spec do
expect(page).to have_content('* (All Environments)')
end
within_status do
expect(find('.project-feature-toggle')['aria-label'])
.to eq('Toggle Status: ON')
end
end
end
context 'when user updates the status of a scope' do
before do
within_scope_row(2) do
within_status { find('.project-feature-toggle').click }
end
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the updated feature flag' do
within_feature_flag_row(1) do
expect(page.find('.feature-flag-name')).to have_content('ci_live_trace')
expect_status_toggle_button_to_be_checked
within_feature_flag_scopes do
expect(page.find('.badge:nth-child(1)')).to have_content('*')
expect(page.find('.badge:nth-child(1)')['class']).to include('badge-info')
expect(page.find('.badge:nth-child(2)')).to have_content('review/*')
expect(page.find('.badge:nth-child(2)')['class']).to include('badge-muted')
end
end
end
end
context 'when user adds a new scope' do
before do
within_scope_row(3) do
within_environment_spec do
find('.js-env-search > input').set('production')
find('.js-create-button').click
end
end
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the newly created scope' do
within_feature_flag_row(1) do
within_feature_flag_scopes do
expect(page.find('.badge:nth-child(3)')).to have_content('production')
expect(page.find('.badge:nth-child(3)')['class']).to include('badge-muted')
end
end
end
end
context 'when user deletes a scope' do
before do
within_scope_row(2) do
within_delete { find('.js-delete-scope').click }
end
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
end
it 'shows the updated feature flag' do
within_feature_flag_row(1) do
within_feature_flag_scopes do
expect(page).to have_css('.badge:nth-child(1)')
expect(page).not_to have_css('.badge:nth-child(2)')
end
end
end
end
end
context 'when legacy flags are read-only' do
it 'the user cannot edit the flag' do
visit(edit_project_feature_flag_path(project, feature_flag))
expect(page).to have_text 'This feature flag is read-only, and it will be removed in 14.0.'
expect(page).to have_css('button.js-ff-submit.disabled')
end
end
context 'when legacy flags are read-only, but the override is active for one project' do
it 'the user can edit the flag' do
stub_feature_flags(feature_flags_legacy_read_only_override: project)
visit(edit_project_feature_flag_path(project, feature_flag))
status_toggle_button.click
click_button 'Save changes'
expect(page).to have_current_path(project_feature_flags_path(project))
within_feature_flag_row(1) do
expect_status_toggle_button_not_to_be_checked
end
end
end
end
end

View File

@ -39,7 +39,7 @@ RSpec.describe 'User edits Release', :js do
it 'renders the edit Release form' do
expect(page).to have_content('Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0, v2.0-pre.')
expect(find_field('Tag name', { disabled: true }).value).to eq(release.tag)
expect(find_field('Tag name', disabled: true).value).to eq(release.tag)
expect(find_field('Release title').value).to eq(release.name)
expect(find_field('Release notes').value).to eq(release.description)

View File

@ -0,0 +1,23 @@
{
"type": "object",
"required" : [
"id",
"name"
],
"properties" : {
"id": { "type": "integer" },
"iid": { "type": ["integer", "null"] },
"version": { "type": "string" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"name": { "type": "string" },
"active": { "type": "boolean" },
"description": { "type": ["string", "null"] },
"edit_path": { "type": ["string", "null"] },
"update_path": { "type": ["string", "null"] },
"destroy_path": { "type": ["string", "null"] },
"scopes": { "type": "array", "items": { "$ref": "feature_flag_scope.json" } },
"strategies": { "type": "array", "items": { "$ref": "feature_flag_strategy.json" } }
},
"additionalProperties": false
}

View File

@ -0,0 +1,18 @@
{
"type": "object",
"required" : [
"id",
"environment_scope",
"active"
],
"properties" : {
"id": { "type": "integer" },
"environment_scope": { "type": "string" },
"active": { "type": "boolean" },
"percentage": { "type": ["integer", "null"] },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"strategies": { "type": "array", "items": { "$ref": "feature_flag_strategy.json" } }
},
"additionalProperties": false
}

Some files were not shown because too many files have changed in this diff Show More