Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
419f9c0ac3
commit
cf37ae7acd
|
@ -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'
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
import vue from './vue';
|
||||
import hcl from './hcl';
|
||||
|
||||
const languages = [vue];
|
||||
const languages = [vue, hcl];
|
||||
|
||||
export default languages;
|
||||
|
|
|
@ -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')"
|
||||
/>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 }) =>
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
- page_title _('Pipelines')
|
||||
- add_page_specific_style 'page_bundles/pipelines'
|
||||
|
||||
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove type column on audit_events table
|
||||
merge_request: 43703
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Restore snippet repositories from backups
|
||||
merge_request: 43696
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Display conan recipe as package name on package detail page
|
||||
merge_request: 44294
|
||||
author:
|
||||
type: changed
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update Design thumbnail after uploading an image with the same filename
|
||||
merge_request: 44305
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move before_script into script for CQ template
|
||||
merge_request: 42782
|
||||
author: Vicken Simonian @vicken.papaya
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: IDE editor - Adding syntax highlighting for terraform / hcl
|
||||
merge_request: 44056
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Introduce required_code_owners_sections table
|
||||
merge_request: 43573
|
||||
author:
|
||||
type: added
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
106757b0f30d3c89fcafa13be92271090fa107831fd538ee087d7ce212842492
|
|
@ -0,0 +1 @@
|
|||
346d0e913212d6e84528d47228ba7e6d0cf4a396e7fc921f7c684acfaaeeedb8
|
|
@ -0,0 +1 @@
|
|||
260f392c3ff257960dc7b198473056e7bf9b9a668403d2f05391d2b7989cf83c
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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
|
|
@ -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!')
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue