Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0254867cf0
commit
a93bf027c2
|
@ -118,8 +118,8 @@
|
|||
- "{,ee/}spec/**/*.rb"
|
||||
|
||||
.db-patterns: &db-patterns
|
||||
- "{,ee/}{db}/**/*"
|
||||
- "{,ee}/spec/{db,migrations}/**/*"
|
||||
- "{,ee/}{,spec/}{db,migrations}/**/*"
|
||||
- "{,ee/}{,spec/}lib/{,ee/}gitlab/background_migration/**/*"
|
||||
|
||||
.backstage-patterns: &backstage-patterns
|
||||
- "Dangerfile"
|
||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<a :href="branchHref" class="btn-link d-flex align-items-center">
|
||||
<span class="d-flex append-right-default ide-search-list-current-icon">
|
||||
<span class="d-flex gl-mr-3 ide-search-list-current-icon">
|
||||
<icon v-if="isActive" :size="18" name="mobile-issue-close" />
|
||||
</span>
|
||||
<span>
|
||||
|
|
|
@ -12,7 +12,7 @@ export default {
|
|||
<div v-if="!lastCommitMsg" class="multi-file-commit-panel-section ide-commit-empty-state">
|
||||
<div class="ide-commit-empty-state-container">
|
||||
<div class="svg-content svg-80"><img :src="noChangesStateSvgPath" /></div>
|
||||
<div class="append-right-default gl-ml-3">
|
||||
<div class="gl-mr-3 gl-ml-3">
|
||||
<div class="text-content text-center">
|
||||
<h4>{{ __('No changes') }}</h4>
|
||||
<p>{{ __('Edit files in the editor and commit changes here') }}</p>
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
<div class="svg-content svg-80">
|
||||
<img :src="committedStateSvgPath" :alt="s__('IDE|Successful commit')" />
|
||||
</div>
|
||||
<div class="append-right-default gl-ml-3">
|
||||
<div class="gl-mr-3 gl-ml-3">
|
||||
<div class="text-content text-center">
|
||||
<h4>{{ __('All changes are committed') }}</h4>
|
||||
<p v-html="lastCommitMsg"></p>
|
||||
|
|
|
@ -48,7 +48,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="d-flex align-items-center ide-file-templates qa-file-templates-bar">
|
||||
<strong class="append-right-default"> {{ __('File templates') }} </strong>
|
||||
<strong class="gl-mr-3"> {{ __('File templates') }} </strong>
|
||||
<dropdown
|
||||
:data="templateTypes"
|
||||
:label="selectedTemplateType.name || __('Choose a type...')"
|
||||
|
|
|
@ -26,7 +26,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="ide-job-item">
|
||||
<job-description :job="job" class="append-right-default" />
|
||||
<job-description :job="job" class="gl-mr-3" />
|
||||
<div class="ml-auto align-self-center">
|
||||
<button v-if="job.started" type="button" class="btn btn-default btn-sm" @click="clickViewLog">
|
||||
{{ __('View log') }}
|
||||
|
|
|
@ -40,7 +40,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<a :href="mergeRequestHref" class="btn-link d-flex align-items-center">
|
||||
<span class="d-flex append-right-default ide-search-list-current-icon">
|
||||
<span class="d-flex gl-mr-3 ide-search-list-current-icon">
|
||||
<icon v-if="isActive" :size="18" name="mobile-issue-close" />
|
||||
</span>
|
||||
<span>
|
||||
|
|
|
@ -102,7 +102,7 @@ export default {
|
|||
class="btn-link d-flex align-items-center"
|
||||
@click.stop="setSearchType(searchType)"
|
||||
>
|
||||
<span class="d-flex append-right-default ide-search-list-current-icon">
|
||||
<span class="d-flex gl-mr-3 ide-search-list-current-icon">
|
||||
<icon :size="18" name="search" />
|
||||
</span>
|
||||
<span>{{ searchType.label }}</span>
|
||||
|
|
|
@ -8,9 +8,10 @@ import ModelManager from './common/model_manager';
|
|||
import { editorOptions, defaultEditorOptions, defaultDiffEditorOptions } from './editor_options';
|
||||
import { themes } from './themes';
|
||||
import languages from './languages';
|
||||
import schemas from './schemas';
|
||||
import keymap from './keymap.json';
|
||||
import { clearDomElement } from '~/editor/utils';
|
||||
import { registerLanguages } from '../utils';
|
||||
import { registerLanguages, registerSchemas } from '../utils';
|
||||
|
||||
function setupThemes() {
|
||||
themes.forEach(theme => {
|
||||
|
@ -44,6 +45,7 @@ export default class Editor {
|
|||
|
||||
setupThemes();
|
||||
registerLanguages(...languages);
|
||||
registerSchemas(...schemas);
|
||||
|
||||
this.debouncedUpdate = debounce(() => {
|
||||
this.updateDimensions();
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import json from './json';
|
||||
import yaml from './yaml';
|
||||
|
||||
export default [json, yaml];
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
language: 'json',
|
||||
options: {
|
||||
validate: true,
|
||||
enableSchemaRequest: true,
|
||||
schemas: [],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
uri: 'https://json.schemastore.org/gitlab-ci',
|
||||
fileMatch: ['*.gitlab-ci.yml'],
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
import gitlabCi from './gitlab_ci';
|
||||
|
||||
export default {
|
||||
language: 'yaml',
|
||||
options: {
|
||||
validate: true,
|
||||
enableSchemaRequest: true,
|
||||
hover: true,
|
||||
completion: true,
|
||||
schemas: [gitlabCi],
|
||||
},
|
||||
};
|
|
@ -66,7 +66,7 @@ export const trimPathComponents = path =>
|
|||
.join('/');
|
||||
|
||||
export function registerLanguages(def, ...defs) {
|
||||
if (defs.length) defs.forEach(lang => registerLanguages(lang));
|
||||
defs.forEach(lang => registerLanguages(lang));
|
||||
|
||||
const languageId = def.id;
|
||||
|
||||
|
@ -75,6 +75,19 @@ export function registerLanguages(def, ...defs) {
|
|||
languages.setLanguageConfiguration(languageId, def.conf);
|
||||
}
|
||||
|
||||
export function registerSchemas({ language, options }, ...schemas) {
|
||||
schemas.forEach(schema => registerSchemas(schema));
|
||||
|
||||
const defaults = {
|
||||
json: languages.json.jsonDefaults,
|
||||
yaml: languages.yaml.yamlDefaults,
|
||||
};
|
||||
|
||||
if (defaults[language]) {
|
||||
defaults[language].setDiagnosticsOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
export const otherSide = side => (side === SIDE_RIGHT ? SIDE_LEFT : SIDE_RIGHT);
|
||||
|
||||
export function trimTrailingWhitespace(content) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<script>
|
||||
import { GlAlert, GlLabel } from '@gitlab/ui';
|
||||
import { last } from 'lodash';
|
||||
import { n__ } from '~/locale';
|
||||
import getIssuesListDetailsQuery from '../queries/get_issues_list_details.query.graphql';
|
||||
import {
|
||||
calculateJiraImportLabel,
|
||||
|
@ -47,6 +49,7 @@ export default {
|
|||
};
|
||||
},
|
||||
update: ({ project }) => ({
|
||||
importedIssuesCount: last(project.jiraImports.nodes)?.importedIssuesCount,
|
||||
isInProgress: isInProgress(project.jiraImportStatus),
|
||||
isFinished: isFinished(project.jiraImportStatus),
|
||||
label: calculateJiraImportLabel(
|
||||
|
@ -60,6 +63,13 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
finishedMessage() {
|
||||
return n__(
|
||||
'%d issue successfully imported with the label',
|
||||
'%d issues successfully imported with the label',
|
||||
this.jiraImport.importedIssuesCount,
|
||||
);
|
||||
},
|
||||
labelTarget() {
|
||||
return `${this.issuesPath}?label_name[]=${encodeURIComponent(this.jiraImport.label.title)}`;
|
||||
},
|
||||
|
@ -87,7 +97,7 @@ export default {
|
|||
{{ __('Import in progress. Refresh page to see newly added issues.') }}
|
||||
</gl-alert>
|
||||
<gl-alert v-if="shouldShowFinishedAlert" variant="success" @dismiss="hideFinishedAlert">
|
||||
{{ __('Issues successfully imported with the label') }}
|
||||
{{ finishedMessage }}
|
||||
<gl-label
|
||||
:background-color="jiraImport.label.color"
|
||||
scoped
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#import "~/jira_import/queries/jira_import.fragment.graphql"
|
||||
|
||||
query($fullPath: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
issues {
|
||||
|
@ -15,7 +13,8 @@ query($fullPath: ID!) {
|
|||
jiraImportStatus
|
||||
jiraImports {
|
||||
nodes {
|
||||
...JiraImport
|
||||
importedIssuesCount
|
||||
jiraProjectKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ export default {
|
|||
v-if="shouldShowDeleteButton"
|
||||
:class="{ disabled: deleteLoading }"
|
||||
:disabled="deleteLoading"
|
||||
class="btn btn-danger float-right append-right-default qa-delete-button"
|
||||
class="btn btn-danger float-right gl-mr-3 qa-delete-button"
|
||||
type="button"
|
||||
@click="deleteIssuable"
|
||||
>
|
||||
|
|
|
@ -72,7 +72,7 @@ export default {
|
|||
<gl-deprecated-button
|
||||
v-if="showBack"
|
||||
size="sm"
|
||||
class="append-right-default js-back-button"
|
||||
class="gl-mr-3 js-back-button"
|
||||
@click="onBackClick"
|
||||
>
|
||||
<icon name="angle-left" />
|
||||
|
|
|
@ -52,7 +52,7 @@ export default {
|
|||
v-if="showReportSectionStatusIcon"
|
||||
:status="status"
|
||||
:status-icon-size="statusIconSize"
|
||||
class="append-right-default"
|
||||
class="gl-mr-3"
|
||||
/>
|
||||
|
||||
<component :is="component" v-if="component" :issue="issue" :status="status" :is-new="isNew" />
|
||||
|
|
|
@ -45,7 +45,7 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="report-block-list-issue report-block-list-issue-parent align-items-center">
|
||||
<div class="report-block-list-icon append-right-default">
|
||||
<div class="report-block-list-icon gl-mr-3">
|
||||
<gl-loading-icon
|
||||
v-if="statusIcon === 'loading'"
|
||||
css-class="report-block-list-loading-icon"
|
||||
|
|
|
@ -59,7 +59,7 @@ export default {
|
|||
|
||||
<template v-else>
|
||||
<button
|
||||
class="btn-blank btn s32 square append-right-default"
|
||||
class="btn-blank btn s32 square gl-mr-3"
|
||||
type="button"
|
||||
:aria-label="ariaLabel"
|
||||
:disabled="isLoading"
|
||||
|
|
|
@ -46,7 +46,7 @@ export default {
|
|||
<div>
|
||||
<div class="mr-widget-body gl-display-flex">
|
||||
<span
|
||||
class="gl-display-flex gl-align-items-center gl-justify-content-center append-right-default gl-align-self-start gl-mt-1"
|
||||
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-mr-3 gl-align-self-start gl-mt-1"
|
||||
>
|
||||
<gl-icon :name="iconName" :size="24" />
|
||||
</span>
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="circle-icon-container append-right-default align-self-start align-self-lg-center">
|
||||
<div class="circle-icon-container gl-mr-3 align-self-start align-self-lg-center">
|
||||
<icon :name="name" :size="24" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -129,7 +129,7 @@ export default {
|
|||
<div class="media-body gl-ml-3" v-html="errorText"></div>
|
||||
</template>
|
||||
<template v-else-if="hasPipeline">
|
||||
<a :href="status.details_path" class="align-self-start append-right-default">
|
||||
<a :href="status.details_path" class="align-self-start gl-mr-3">
|
||||
<ci-icon :status="status" :size="24" :borderless="true" class="add-border" />
|
||||
</a>
|
||||
<div class="ci-widget-container d-flex">
|
||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="d-flex align-self-start">
|
||||
<div class="square s24 h-auto d-flex-center append-right-default">
|
||||
<div class="square s24 h-auto d-flex-center gl-mr-3">
|
||||
<div v-if="isLoading" class="mr-widget-icon d-inline-flex">
|
||||
<gl-loading-icon size="md" class="mr-loading-icon d-inline-flex" />
|
||||
</div>
|
||||
|
|
|
@ -83,7 +83,7 @@ export default {
|
|||
<gl-deprecated-button
|
||||
:aria-label="ariaLabel"
|
||||
variant="blank"
|
||||
class="commit-edit-toggle square s24 append-right-default"
|
||||
class="commit-edit-toggle square s24 gl-mr-3"
|
||||
@click.stop="toggle()"
|
||||
>
|
||||
<icon :name="collapseIcon" :size="16" />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -25,12 +26,22 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
tooltipTitle() {
|
||||
return this.isDisabled ? __('Required in this project.') : false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline">
|
||||
<label :class="{ 'gl-text-gray-600': isDisabled }" data-testid="squashLabel">
|
||||
<label
|
||||
v-tooltip
|
||||
:class="{ 'gl-text-gray-600': isDisabled }"
|
||||
data-testid="squashLabel"
|
||||
:data-title="tooltipTitle"
|
||||
>
|
||||
<input
|
||||
:checked="value"
|
||||
:disabled="isDisabled"
|
||||
|
|
|
@ -58,7 +58,7 @@ export default {
|
|||
<template>
|
||||
<div class="gl-display-flex">
|
||||
<span
|
||||
class="gl-display-flex gl-align-items-center gl-justify-content-center append-right-default gl-align-self-start gl-mt-1"
|
||||
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-mr-3 gl-align-self-start gl-mt-1"
|
||||
>
|
||||
<gl-icon :name="iconType" :size="18" data-testid="change-type-icon" />
|
||||
</span>
|
||||
|
|
|
@ -261,7 +261,7 @@ export default {
|
|||
</li>
|
||||
</template>
|
||||
<li v-else class="dropdown-menu-empty-item">
|
||||
<div class="append-right-default gl-ml-3 gl-mt-3 gl-mb-3">
|
||||
<div class="gl-mr-3 gl-ml-3 gl-mt-3 gl-mb-3">
|
||||
<template v-if="loading">
|
||||
{{ __('Loading...') }}
|
||||
</template>
|
||||
|
|
|
@ -53,14 +53,9 @@ export default {
|
|||
},
|
||||
tooltipMessage() {
|
||||
return this.canApply
|
||||
? __('This also resolves the discussion')
|
||||
? __('This also resolves this thread')
|
||||
: __("Can't apply as this line has changed or the suggestion already matches its content.");
|
||||
},
|
||||
tooltipMessageBatch() {
|
||||
return !this.canBeBatched
|
||||
? __("Suggestions that change line count can't be added to batches, yet.")
|
||||
: this.tooltipMessage;
|
||||
},
|
||||
isDisableButton() {
|
||||
return this.isApplying || !this.canApply;
|
||||
},
|
||||
|
@ -129,15 +124,14 @@ export default {
|
|||
</gl-deprecated-button>
|
||||
</div>
|
||||
<div v-else class="d-flex align-items-center">
|
||||
<span v-if="canBeBatched" v-gl-tooltip.viewport="tooltipMessageBatch" tabindex="0">
|
||||
<gl-deprecated-button
|
||||
class="btn-inverted js-add-to-batch-btn btn-grouped"
|
||||
:disabled="isDisableButton"
|
||||
@click="addSuggestionToBatch"
|
||||
>
|
||||
{{ __('Add suggestion to batch') }}
|
||||
</gl-deprecated-button>
|
||||
</span>
|
||||
<gl-deprecated-button
|
||||
v-if="canBeBatched && !isDisableButton"
|
||||
class="btn-inverted js-add-to-batch-btn btn-grouped"
|
||||
:disabled="isDisableButton"
|
||||
@click="addSuggestionToBatch"
|
||||
>
|
||||
{{ __('Add suggestion to batch') }}
|
||||
</gl-deprecated-button>
|
||||
<span v-gl-tooltip.viewport="tooltipMessage" tabindex="0">
|
||||
<gl-deprecated-button
|
||||
class="btn-inverted js-apply-btn btn-grouped"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import renderKramdownList from './renderers/render_kramdown_list';
|
||||
import renderKramdownText from './renderers/render_kramdown_text';
|
||||
import renderIdentifierText from './renderers/render_identifier_text';
|
||||
import renderIdentifierParagraph from './renderers/render_identifier_paragraph';
|
||||
import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text';
|
||||
|
||||
const listRenderers = [renderKramdownList];
|
||||
const textRenderers = [renderKramdownText, renderIdentifierText, renderEmbeddedRubyText];
|
||||
const paragraphRenderers = [renderIdentifierParagraph];
|
||||
const textRenderers = [renderKramdownText, renderEmbeddedRubyText];
|
||||
|
||||
const executeRenderer = (renderers, node, context) => {
|
||||
const availableRenderer = renderers.find(renderer => renderer.canRender(node, context));
|
||||
|
@ -22,13 +23,18 @@ const buildCustomRendererFunctions = (customRenderers, defaults) => {
|
|||
return Object.fromEntries(customEntries);
|
||||
};
|
||||
|
||||
const buildCustomHTMLRenderer = (customRenderers = { list: [], text: [] }) => {
|
||||
const buildCustomHTMLRenderer = (customRenderers = { list: [], paragraph: [], text: [] }) => {
|
||||
const defaults = {
|
||||
list(node, context) {
|
||||
const allListRenderers = [...customRenderers.list, ...listRenderers];
|
||||
|
||||
return executeRenderer(allListRenderers, node, context);
|
||||
},
|
||||
paragraph(node, context) {
|
||||
const allParagraphRenderers = [...customRenderers.list, ...paragraphRenderers];
|
||||
|
||||
return executeRenderer(allParagraphRenderers, node, context);
|
||||
},
|
||||
text(node, context) {
|
||||
const allTextRenderers = [...customRenderers.text, ...textRenderers];
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { buildUneditableOpenTokens, buildUneditableCloseToken } from './build_uneditable_token';
|
||||
|
||||
const identifierRegex = /(^\[.+\]: .+)/;
|
||||
|
||||
const isIdentifier = text => {
|
||||
return identifierRegex.test(text);
|
||||
};
|
||||
|
||||
const canRender = (node, context) => {
|
||||
return isIdentifier(context.getChildrenText(node));
|
||||
};
|
||||
|
||||
const render = (_, { entering, origin }) =>
|
||||
entering ? buildUneditableOpenTokens(origin()) : buildUneditableCloseToken();
|
||||
|
||||
export default { canRender, render };
|
|
@ -1,81 +0,0 @@
|
|||
import {
|
||||
buildUneditableOpenTokens,
|
||||
buildUneditableCloseTokens,
|
||||
buildUneditableTokens,
|
||||
} from './build_uneditable_token';
|
||||
|
||||
const identifierRegex = /(^\[.+\]: .+)/;
|
||||
|
||||
const isBasicIdentifier = ({ literal }) => {
|
||||
return identifierRegex.test(literal);
|
||||
};
|
||||
|
||||
const isInlineCodeNode = ({ type, tickCount }) => type === 'code' && tickCount === 1;
|
||||
|
||||
const hasAdjacentInlineCode = (isForward, node) => {
|
||||
const direction = isForward ? 'next' : 'prev';
|
||||
|
||||
let currentNode = node;
|
||||
while (currentNode[direction] && currentNode.literal !== null) {
|
||||
if (isInlineCodeNode(currentNode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
currentNode = currentNode[direction];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const hasEnteringPotential = literal => literal.includes('[');
|
||||
const hasExitingPotential = literal => literal.includes(']: ');
|
||||
|
||||
const hasAdjacentExit = node => {
|
||||
let currentNode = node;
|
||||
|
||||
while (currentNode && currentNode.literal !== null) {
|
||||
if (hasExitingPotential(currentNode.literal)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
currentNode = currentNode.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isEnteringWithAdjacentInlineCode = ({ literal, next }) => {
|
||||
if (next && hasEnteringPotential(literal) && !hasExitingPotential(literal)) {
|
||||
return hasAdjacentInlineCode(true, next) && hasAdjacentExit(next);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isExitingWithAdjacentInlineCode = ({ literal, prev }) => {
|
||||
if (prev && !hasEnteringPotential(literal) && hasExitingPotential(literal)) {
|
||||
return hasAdjacentInlineCode(false, prev);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isAdjacentInlineCodeIdentifier = node => {
|
||||
return isEnteringWithAdjacentInlineCode(node) || isExitingWithAdjacentInlineCode(node);
|
||||
};
|
||||
|
||||
const canRender = (node, context) => {
|
||||
return isBasicIdentifier(node) || isAdjacentInlineCodeIdentifier(node, context);
|
||||
};
|
||||
|
||||
const render = (node, { origin }) => {
|
||||
if (isEnteringWithAdjacentInlineCode(node)) {
|
||||
return buildUneditableOpenTokens(origin());
|
||||
} else if (isExitingWithAdjacentInlineCode(node)) {
|
||||
return buildUneditableCloseTokens(origin());
|
||||
}
|
||||
|
||||
return buildUneditableTokens(origin());
|
||||
};
|
||||
|
||||
export default { canRender, render };
|
|
@ -408,7 +408,6 @@ img.emoji {
|
|||
.append-right-5 { margin-right: 5px; }
|
||||
.append-right-10 { margin-right: 10px; }
|
||||
.append-right-15 { margin-right: 15px; }
|
||||
.append-right-default { margin-right: $gl-padding; }
|
||||
.append-right-20 { margin-right: 20px; }
|
||||
.append-bottom-10 { margin-bottom: 10px; }
|
||||
.append-bottom-20 { margin-bottom: 20px; }
|
||||
|
|
|
@ -321,7 +321,13 @@ module ApplicationSettingsHelper
|
|||
:email_restrictions_enabled,
|
||||
:email_restrictions,
|
||||
:issues_create_limit,
|
||||
:raw_blob_request_limit
|
||||
:raw_blob_request_limit,
|
||||
:project_import_limit,
|
||||
:project_export_limit,
|
||||
:project_download_export_limit,
|
||||
:group_import_limit,
|
||||
:group_export_limit,
|
||||
:group_download_export_limit
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -158,7 +158,13 @@ module ApplicationSettingImplementation
|
|||
snowplow_iglu_registry_url: nil,
|
||||
custom_http_clone_url_root: nil,
|
||||
productivity_analytics_start_date: Time.current,
|
||||
snippet_size_limit: 50.megabytes
|
||||
snippet_size_limit: 50.megabytes,
|
||||
project_import_limit: 6,
|
||||
project_export_limit: 6,
|
||||
project_download_export_limit: 1,
|
||||
group_import_limit: 6,
|
||||
group_export_limit: 6,
|
||||
group_download_export_limit: 1
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -75,7 +75,12 @@ class ProjectStatistics < ApplicationRecord
|
|||
end
|
||||
|
||||
def update_storage_size
|
||||
self.storage_size = repository_size + wiki_size + lfs_objects_size + build_artifacts_size + packages_size + snippets_size
|
||||
storage_size = repository_size + wiki_size + lfs_objects_size + build_artifacts_size + packages_size
|
||||
# The `snippets_size` column was added on 20200622095419 but db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
|
||||
# might try to update project statistics before the `snippets_size` column has been created.
|
||||
storage_size += snippets_size if self.class.column_names.include?('snippets_size')
|
||||
|
||||
self.storage_size = storage_size
|
||||
end
|
||||
|
||||
# Since this incremental update method does not call update_storage_size above,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-import-export-limits-settings'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :project_import_limit, _('Max Project Import requests per minute per user'), class: 'label-bold'
|
||||
= f.number_field :project_import_limit, class: 'form-control'
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :project_export_limit, _('Max Project Export requests per minute per user'), class: 'label-bold'
|
||||
= f.number_field :project_export_limit, class: 'form-control'
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :project_download_export_limit, _('Max Project Export Download requests per minute per user'), class: 'label-bold'
|
||||
= f.number_field :project_download_export_limit, class: 'form-control'
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :group_import_limit, _('Max Group Import requests per minute per user'), class: 'label-bold'
|
||||
= f.number_field :group_import_limit, class: 'form-control'
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :group_export_limit, _('Max Group Export requests per minute per user'), class: 'label-bold'
|
||||
= f.number_field :group_export_limit, class: 'form-control'
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :group_download_export_limit, _('Max Group Export Download requests per minute per user'), class: 'label-bold'
|
||||
= f.number_field :group_download_export_limit, class: 'form-control'
|
||||
|
||||
= f.submit 'Save changes', class: "btn btn-success", data: { qa_selector: 'save_changes_button' }
|
|
@ -57,4 +57,15 @@
|
|||
.settings-content
|
||||
= render 'issue_limits'
|
||||
|
||||
%section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||
.settings-header
|
||||
%h4
|
||||
= _('Import/Export Rate Limits')
|
||||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Configure limits for Project/Group Import/Export.')
|
||||
.settings-content
|
||||
= render 'import_export_limits'
|
||||
|
||||
= render_if_exists 'admin/application_settings/ee_network_settings'
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'mask-a-custom-variable'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- unless only_key_value
|
||||
.ci-variable-body-item.ci-variable-protected-item.table-section.section-20.mr-0.border-top-0
|
||||
.append-right-default
|
||||
.gl-mr-3
|
||||
= s_("CiVariable|Protected")
|
||||
= render "shared/buttons/project_feature_toggle", is_checked: is_protected, label: s_("CiVariable|Toggle protected") do
|
||||
%input{ type: "hidden",
|
||||
|
@ -51,7 +51,7 @@
|
|||
value: is_protected,
|
||||
data: { default: is_protected_default.to_s } }
|
||||
.ci-variable-body-item.ci-variable-masked-item.table-section.section-20.mr-0.border-top-0
|
||||
.append-right-default
|
||||
.gl-mr-3
|
||||
= s_("CiVariable|Masked")
|
||||
= render "shared/buttons/project_feature_toggle", is_checked: is_masked, label: s_("CiVariable|Toggle masked"), class_list: "js-project-feature-toggle project-feature-toggle qa-variable-masked" do
|
||||
%input{ type: "hidden",
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
.nav-controls
|
||||
- if @todos.any?(&:pending?)
|
||||
.append-right-default
|
||||
.gl-mr-3
|
||||
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading d-flex align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
|
||||
Mark all as done
|
||||
%span.spinner.ml-1
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
.group-home-panel
|
||||
.row.mb-3
|
||||
.home-panel-title-row.col-md-12.col-lg-6.d-flex
|
||||
.avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
|
||||
.avatar-container.rect-avatar.s64.home-panel-avatar.gl-mr-3.float-none
|
||||
= group_icon(@group, class: 'avatar avatar-tile s64', width: 64, height: 64)
|
||||
.d-flex.flex-column.flex-wrap.align-items-baseline
|
||||
.d-inline-flex.align-items-baseline
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
.project-home-panel.js-show-on-project-root{ class: [("empty-project" if empty_repo)] }
|
||||
.row.gl-mb-3
|
||||
.home-panel-title-row.col-md-12.col-lg-6.d-flex
|
||||
.avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
|
||||
.avatar-container.rect-avatar.s64.home-panel-avatar.gl-mr-3.float-none
|
||||
= project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64)
|
||||
.d-flex.flex-column.flex-wrap.align-items-baseline
|
||||
.d-inline-flex.align-items-baseline
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- can_create_project = current_user.can?(:create_projects, namespace)
|
||||
|
||||
- if forked_project = namespace.find_fork_of(@project)
|
||||
.bordered-box.fork-thumbnail.text-center.gl-ml-3.append-right-default.gl-mt-3.gl-mb-3.forked
|
||||
.bordered-box.fork-thumbnail.text-center.gl-ml-3.gl-mr-3.gl-mt-3.gl-mb-3.forked
|
||||
= link_to project_path(forked_project) do
|
||||
- if /no_((\w*)_)*avatar/.match(avatar)
|
||||
= group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto")
|
||||
|
@ -12,7 +12,7 @@
|
|||
%h5.gl-mt-3
|
||||
= namespace.human_name
|
||||
- else
|
||||
.bordered-box.fork-thumbnail.text-center.gl-ml-3.append-right-default.gl-mt-3.gl-mb-3{ class: ("disabled" unless can_create_project) }
|
||||
.bordered-box.fork-thumbnail.text-center.gl-ml-3.gl-mr-3.gl-mt-3.gl-mb-3{ class: ("disabled" unless can_create_project) }
|
||||
= link_to project_forks_path(@project, namespace_key: namespace.id),
|
||||
method: "POST",
|
||||
class: ("disabled has-tooltip" unless can_create_project),
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
.col-lg-9
|
||||
- if @namespaces.present?
|
||||
.fork-thumbnail-container.js-fork-content
|
||||
%h5.gl-mt-0.gl-mb-0.gl-ml-3.append-right-default
|
||||
%h5.gl-mt-0.gl-mb-0.gl-ml-3.gl-mr-3
|
||||
= _("Select a namespace to fork the project")
|
||||
- @namespaces.each do |namespace|
|
||||
= render 'fork_button', namespace: namespace
|
||||
|
|
|
@ -58,10 +58,10 @@
|
|||
= render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: project
|
||||
|
||||
- if show_last_commit_as_description
|
||||
.description.d-none.d-sm-block.append-right-default
|
||||
.description.d-none.d-sm-block.gl-mr-3
|
||||
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
|
||||
- elsif project.description.present?
|
||||
.description.d-none.d-sm-block.append-right-default
|
||||
.description.d-none.d-sm-block.gl-mr-3
|
||||
= markdown_field(project, :description)
|
||||
|
||||
.controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add support for linting based on schemas in WebIDE
|
||||
merge_request: 35838
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add temporary storage increase column
|
||||
merge_request: 36107
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add count to imported Jira issues message
|
||||
merge_request: 36075
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Convert Import/Export rate limits to configurable application settings
|
||||
merge_request: 35728
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Reduce number of scanned commits for code intelligence
|
||||
merge_request: 36093
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: FindRemoteRepository is storage scoped
|
||||
merge_request: 35962
|
||||
author:
|
||||
type: added
|
|
@ -501,11 +501,6 @@ production: &base
|
|||
geo_registry_sync_worker:
|
||||
cron: "*/1 * * * *"
|
||||
|
||||
# GitLab Geo migrated local files clean up worker
|
||||
# NOTE: This will only take effect if Geo is enabled (secondary nodes only)
|
||||
geo_migrated_local_files_clean_up_worker:
|
||||
cron: "15 */6 * * *"
|
||||
|
||||
# Export pseudonymized data in CSV format for analysis
|
||||
pseudonymizer_worker:
|
||||
cron: "0 * * * *"
|
||||
|
@ -514,7 +509,7 @@ production: &base
|
|||
# NOTE: This will only take effect if elasticsearch is enabled.
|
||||
elastic_index_bulk_cron_worker:
|
||||
cron: "*/1 * * * *"
|
||||
|
||||
|
||||
# Elasticsearch bulk updater for initial updates.
|
||||
# NOTE: This will only take effect if elasticsearch is enabled.
|
||||
elastic_index_initial_bulk_cron_worker:
|
||||
|
|
|
@ -522,9 +522,6 @@ Gitlab.ee do
|
|||
Settings.cron_jobs['geo_metrics_update_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['geo_metrics_update_worker']['cron'] ||= '*/1 * * * *'
|
||||
Settings.cron_jobs['geo_metrics_update_worker']['job_class'] ||= 'Geo::MetricsUpdateWorker'
|
||||
Settings.cron_jobs['geo_migrated_local_files_clean_up_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['geo_migrated_local_files_clean_up_worker']['cron'] ||= '15 */6 * * *'
|
||||
Settings.cron_jobs['geo_migrated_local_files_clean_up_worker']['job_class'] ||= 'Geo::MigratedLocalFilesCleanUpWorker'
|
||||
Settings.cron_jobs['geo_prune_event_log_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['geo_prune_event_log_worker']['cron'] ||= '*/5 * * * *'
|
||||
Settings.cron_jobs['geo_prune_event_log_worker']['job_class'] ||= 'Geo::PruneEventLogWorker'
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
const { languagesArr } = require('monaco-editor-webpack-plugin/out/languages');
|
||||
|
||||
// monaco-yaml library doesn't play so well with monaco-editor-webpack-plugin
|
||||
// so the only way to include its workers is by patching the list of languages
|
||||
// in monaco-editor-webpack-plugin and adding support for yaml workers. This is
|
||||
// a known issue in the library and this workaround was suggested here:
|
||||
// https://github.com/pengx17/monaco-yaml/issues/20
|
||||
|
||||
const yamlLang = languagesArr.find(t => t.label === 'yaml');
|
||||
|
||||
yamlLang.entry = [yamlLang.entry, '../../monaco-yaml/esm/monaco.contribution'];
|
||||
yamlLang.worker = {
|
||||
id: 'vs/language/yaml/yamlWorker',
|
||||
entry: '../../monaco-yaml/esm/yaml.worker.js',
|
||||
};
|
||||
|
||||
module.exports = require('monaco-editor-webpack-plugin');
|
|
@ -5,7 +5,7 @@ const webpack = require('webpack');
|
|||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||
const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
|
||||
const MonacoWebpackPlugin = require('./plugins/monaco_webpack');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const vendorDllHash = require('./helpers/vendor_dll_hash');
|
||||
|
@ -241,7 +241,7 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
test: /\.(eot|ttf|woff|woff2)$/,
|
||||
include: /node_modules\/katex\/dist\/fonts/,
|
||||
include: /node_modules\/(katex\/dist\/fonts|monaco-editor)/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[contenthash:8].[ext]',
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StealEncryptRunnersTokens < ActiveRecord::Migration[5.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
# This cleans after `EncryptRunnersTokens`
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
Gitlab::BackgroundMigration.steal('EncryptRunnersTokens')
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddImportExportLimitsToApplicationSettings < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :application_settings, :project_import_limit, :integer, default: 6, null: false
|
||||
add_column :application_settings, :project_export_limit, :integer, default: 6, null: false
|
||||
add_column :application_settings, :project_download_export_limit, :integer, default: 1, null: false
|
||||
|
||||
add_column :application_settings, :group_import_limit, :integer, default: 6, null: false
|
||||
add_column :application_settings, :group_export_limit, :integer, default: 6, null: false
|
||||
add_column :application_settings, :group_download_export_limit, :integer, default: 1, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTemporaryStorageIncreaseToNamespaceLimits < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :namespace_limits, :temporary_storage_increase_ends_on, :date, null: true
|
||||
end
|
||||
end
|
|
@ -9145,6 +9145,12 @@ CREATE TABLE public.application_settings (
|
|||
compliance_frameworks smallint[] DEFAULT '{}'::smallint[] NOT NULL,
|
||||
notify_on_unknown_sign_in boolean DEFAULT true NOT NULL,
|
||||
default_branch_name text,
|
||||
project_import_limit integer DEFAULT 6 NOT NULL,
|
||||
project_export_limit integer DEFAULT 6 NOT NULL,
|
||||
project_download_export_limit integer DEFAULT 1 NOT NULL,
|
||||
group_import_limit integer DEFAULT 6 NOT NULL,
|
||||
group_export_limit integer DEFAULT 6 NOT NULL,
|
||||
group_download_export_limit integer DEFAULT 1 NOT NULL,
|
||||
CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)),
|
||||
CONSTRAINT check_d03919528d CHECK ((char_length(container_registry_vendor) <= 255)),
|
||||
CONSTRAINT check_d820146492 CHECK ((char_length(spam_check_endpoint_url) <= 255)),
|
||||
|
@ -13014,7 +13020,8 @@ CREATE TABLE public.namespace_aggregation_schedules (
|
|||
CREATE TABLE public.namespace_limits (
|
||||
additional_purchased_storage_size bigint DEFAULT 0 NOT NULL,
|
||||
additional_purchased_storage_ends_on date,
|
||||
namespace_id integer NOT NULL
|
||||
namespace_id integer NOT NULL,
|
||||
temporary_storage_increase_ends_on date
|
||||
);
|
||||
|
||||
CREATE TABLE public.namespace_root_storage_statistics (
|
||||
|
@ -22523,7 +22530,6 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20190220150130
|
||||
20190222051615
|
||||
20190225152525
|
||||
20190225160300
|
||||
20190225160301
|
||||
20190228192410
|
||||
20190301081611
|
||||
|
@ -23576,9 +23582,11 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200626060151
|
||||
20200626130220
|
||||
20200630110826
|
||||
20200701093859
|
||||
20200702123805
|
||||
20200703154822
|
||||
20200704143633
|
||||
20200706005325
|
||||
20200706170536
|
||||
\.
|
||||
|
||||
|
|
|
@ -261,8 +261,8 @@ instance (`cache`, `shared_state` etc.).
|
|||
|
||||
| Metric | Type | Since | Description |
|
||||
|:--------------------------------- |:------- |:----- |:----------- |
|
||||
| `redis_client_exceptions_total` | Counter | 13.2 | Number of Redis client exceptions, broken down by exception class |
|
||||
| `redis_client_requests_total` | Counter | 13.2 | Number of Redis client requests |
|
||||
| `gitlab_redis_client_exceptions_total` | Counter | 13.2 | Number of Redis client exceptions, broken down by exception class |
|
||||
| `gitlab_redis_client_requests_total` | Counter | 13.2 | Number of Redis client requests |
|
||||
|
||||
## Metrics shared directory
|
||||
|
||||
|
|
|
@ -667,8 +667,8 @@ Parameters:
|
|||
| `request_access_enabled` | boolean | no | Allow users to request member access. |
|
||||
| `parent_id` | integer | no | The parent group ID for creating nested group. |
|
||||
| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). Default to the global level default branch protection setting. |
|
||||
| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group. |
|
||||
| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group. |
|
||||
| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` |
|
||||
| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group (purchased in addition to the minutes included in the plan). |
|
||||
|
||||
### Options for `default_branch_protection`
|
||||
|
||||
|
@ -741,8 +741,8 @@ PUT /groups/:id
|
|||
| `request_access_enabled` | boolean | no | Allow users to request member access. |
|
||||
| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). |
|
||||
| `file_template_project_id` | integer | no | **(PREMIUM)** The ID of a project to load custom file templates from. |
|
||||
| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group. |
|
||||
| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group. |
|
||||
| `shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Pipeline minutes quota for this group (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` |
|
||||
| `extra_shared_runners_minutes_limit` | integer | no | **(STARTER ONLY)** Extra pipeline minutes quota for this group (purchased in addition to the minutes included in the plan). |
|
||||
|
||||
NOTE: **Note:**
|
||||
The `projects` and `shared_projects` attributes in the response are deprecated and will be [removed in API v5](https://gitlab.com/gitlab-org/gitlab/-/issues/213797).
|
||||
|
|
|
@ -384,7 +384,7 @@ Parameters:
|
|||
| `email` | Yes | Email |
|
||||
| `extern_uid` | No | External UID |
|
||||
| `external` | No | Flags the user as external - true or false (default) |
|
||||
| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user **(STARTER)** |
|
||||
| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(STARTER)** |
|
||||
| `force_random_password` | No | Set user password to a random value - true or false (default) |
|
||||
| `group_id_for_saml` | No | ID of group where SAML has been configured |
|
||||
| `linkedin` | No | LinkedIn |
|
||||
|
@ -398,7 +398,7 @@ Parameters:
|
|||
| `provider` | No | External provider name |
|
||||
| `public_email` | No | The public email of the user |
|
||||
| `reset_password` | No | Send user password reset link - true or false(default) |
|
||||
| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user **(STARTER)** |
|
||||
| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(STARTER)** |
|
||||
| `skip_confirmation` | No | Skip confirmation - true or false (default) |
|
||||
| `skype` | No | Skype ID |
|
||||
| `theme_id` | No | The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information) |
|
||||
|
@ -426,7 +426,7 @@ Parameters:
|
|||
| `email` | No | Email |
|
||||
| `extern_uid` | No | External UID |
|
||||
| `external` | No | Flags the user as external - true or false (default) |
|
||||
| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user **(STARTER)** |
|
||||
| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(STARTER)** |
|
||||
| `group_id_for_saml` | No | ID of group where SAML has been configured |
|
||||
| `id` | Yes | The ID of the user |
|
||||
| `linkedin` | No | LinkedIn |
|
||||
|
@ -439,7 +439,7 @@ Parameters:
|
|||
| `projects_limit` | No | Limit projects each user can create |
|
||||
| `provider` | No | External provider name |
|
||||
| `public_email` | No | The public email of the user |
|
||||
| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user **(STARTER)** |
|
||||
| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(STARTER)** |
|
||||
| `skip_reconfirmation` | No | Skip reconfirmation - true or false (default) |
|
||||
| `skype` | No | Skype ID |
|
||||
| `theme_id` | No | The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information) |
|
||||
|
|
|
@ -580,13 +580,14 @@ Make sure the IdP provides a claim containing the user's email address, using cl
|
|||
If after signing in into your SAML server you are redirected back to the sign in page and
|
||||
no error is displayed, check your `production.log` file. It will most likely contain the
|
||||
message `Can't verify CSRF token authenticity`. This means that there is an error during
|
||||
the SAML request, but this error never reaches GitLab due to the CSRF check.
|
||||
the SAML request, but in GitLab 11.7 and earlier this error never reaches GitLab due to
|
||||
the CSRF check.
|
||||
|
||||
To bypass this you can add `skip_before_action :verify_authenticity_token` to the
|
||||
`omniauth_callbacks_controller.rb` file immediately after the `class` line and
|
||||
comment out the `protect_from_forgery` line using a `#` then restart Unicorn. This
|
||||
will allow the error to hit GitLab, where it can then be seen in the usual logs,
|
||||
or as a flash message on the login screen.
|
||||
comment out the `protect_from_forgery` line using a `#`. Restart Unicorn for this
|
||||
change to take effect. This will allow the error to hit GitLab, where it can then
|
||||
be seen in the usual logs, or as a flash message on the login screen.
|
||||
|
||||
That file is located in `/opt/gitlab/embedded/service/gitlab-rails/app/controllers`
|
||||
for Omnibus installations and by default in `/home/git/gitlab/app/controllers` for
|
||||
|
|
|
@ -19,8 +19,8 @@ GitLab uses [Semantic Versioning](https://semver.org/) for its releases:
|
|||
|
||||
For example, for GitLab version 12.10.6:
|
||||
|
||||
- `12` represents the major version. The major release was 12.0.0, but often referred to as 12.0.
|
||||
- `10` represents the minor version. The minor release was 12.10.0, but often referred to as 12.10.
|
||||
- `12` represents the major version. The major release was 12.0.0 but often referred to as 12.0.
|
||||
- `10` represents the minor version. The minor release was 12.10.0 but often referred to as 12.10.
|
||||
- `6` represents the patch number.
|
||||
|
||||
Any part of the version number can increment into multiple digits, for example, 13.10.11.
|
||||
|
@ -37,7 +37,7 @@ The following table describes the version types and their release cadence:
|
|||
|
||||
We encourage everyone to run the [latest stable release](https://about.gitlab.com/releases/categories/releases/)
|
||||
to ensure that you can easily upgrade to the most secure and feature-rich GitLab experience.
|
||||
In order to make sure you can easily run the most recent stable release, we are working
|
||||
To make sure you can easily run the most recent stable release, we are working
|
||||
hard to keep the update process simple and reliable.
|
||||
|
||||
If you are unable to follow our monthly release cycle, there are a couple of
|
||||
|
@ -79,7 +79,7 @@ We cannot guarantee that upgrading between major versions will be seamless.
|
|||
We suggest upgrading to the latest available *minor* version within
|
||||
your major version before proceeding to the next major version.
|
||||
Doing this will address any backward-incompatible changes or deprecations
|
||||
to help ensure a successful upgrade to next major release.
|
||||
to help ensure a successful upgrade to the next major release.
|
||||
|
||||
It's also important to ensure that any background migrations have been fully completed
|
||||
before upgrading to a new major version. To see the current size of the `background_migration` queue,
|
||||
|
@ -110,14 +110,13 @@ Please see the table below for some examples:
|
|||
| `13.2.0` | `11.5.0` | `11.5.0` -> `11.11.8` -> `12.0.12` -> `12.10.6` -> `13.0.0` -> `13.2.0` | Four intermediate versions are required: the final `11.11`, `12.0`, and `12.10` releases, plus `13.0`. |
|
||||
| `13.0.1` | `11.10.8` | `11.10.5` -> `11.11.8` -> `12.0.12` -> `12.10.6` -> `13.0.1` | Three intermediate versions are required: `11.11`, `12.0`, and `12.10`. |
|
||||
| `12.10.6` | `11.3.4` | `11.3.4` -> `11.11.8` -> `12.0.12` -> `12.10.6` | Two intermediate versions are required: `11.11` and `12.0` |
|
||||
| `12.9.5.` | `10.4.5` | `10.4.5` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.9.5` | Three intermediate versions are required: `10.8`, `11.11`, and `12.0`, then `12.9.5` |
|
||||
| `12.9.5` | `10.4.5` | `10.4.5` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.9.5` | Three intermediate versions are required: `10.8`, `11.11`, and `12.0`, then `12.9.5` |
|
||||
| `12.2.5` | `9.2.6` | `9.2.6` -> `9.5.10` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.2.5` | Four intermediate versions are required: `9.5`, `10.8`, `11.11`, `12.0`, then `12.2`. |
|
||||
| `11.3.4` | `8.13.4` | `8.13.4` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> `11.3.4` | `8.17.7` is the last version in version 8, `9.5.10` is the last version in version 9, `10.8.7` is the last version in version 10. |
|
||||
|
||||
### Upgrades from versions earlier than 8.12
|
||||
|
||||
- `8.11.x` and earlier: you might have to upgrade to `8.12.0` specifically before you can
|
||||
upgrade to `8.17.7`. This was [reported in an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/207259).
|
||||
- `8.11.x` and earlier: you might have to upgrade to `8.12.0` specifically before you can upgrade to `8.17.7`. This was [reported in an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/207259).
|
||||
- [CI changes prior to version 8.0](https://docs.gitlab.com/omnibus/update/README.html#updating-gitlab-ci-from-prior-540-to-version-714-via-omnibus-gitlab)
|
||||
when it was merged into GitLab.
|
||||
|
||||
|
@ -155,11 +154,11 @@ and support cost.
|
|||
1. Supporting parallel version discourages incremental upgrades which over time accumulate in
|
||||
complexity and create upgrade challenges for all users. GitLab has a dedicated team ensuring that
|
||||
incremental upgrades (and installations) are as simple as possible.
|
||||
1. The number of changes created in the GitLab application is high, which contributes to backporting complexity to older releases. In number of cases, backporting has to go through the same
|
||||
1. The number of changes created in the GitLab application is high, which contributes to backporting complexity to older releases. In several cases, backporting has to go through the same
|
||||
review process a new change goes through.
|
||||
1. Ensuring that tests pass on older release is a considerable challenge in some cases, and as such is very time consuming.
|
||||
1. Ensuring that tests pass on the older release is a considerable challenge in some cases, and as such is very time-consuming.
|
||||
|
||||
Including new features in patch releases is not possible as that would break [Semantic Versioning](https://semver.org/).
|
||||
Including new features in a patch release is not possible as that would break [Semantic Versioning](https://semver.org/).
|
||||
Breaking [Semantic Versioning](https://semver.org/) has the following consequences for users that
|
||||
have to adhere to various internal requirements (for example, org. compliance, verifying new features, and similar):
|
||||
|
||||
|
@ -169,7 +168,7 @@ have to adhere to various internal requirements (for example, org. compliance, v
|
|||
|
||||
In cases where a strategic user has a requirement to test a feature before it is
|
||||
officially released, we can offer to create a Release Candidate (RC) version that will
|
||||
include the specific feature. This should be needed only in extreme cases, and can be requested for
|
||||
include the specific feature. This should be needed only in extreme cases and can be requested for
|
||||
consideration by raising an issue in the [release/tasks](https://gitlab.com/gitlab-org/release/tasks/-/issues/new?issuable_template=Backporting-request) issue tracker.
|
||||
It is important to note that the Release Candidate will also contain other features and changes as
|
||||
it is not possible to easily isolate a specific feature (similar reasons as noted above). The
|
||||
|
@ -179,7 +178,7 @@ accessible.
|
|||
### Backporting to older releases
|
||||
|
||||
Backporting to more than one stable release is reserved for [security releases](#security-releases).
|
||||
In some cases however, we may need to backport *a bug fix* to more than one stable
|
||||
In some cases, however, we may need to backport *a bug fix* to more than one stable
|
||||
release, depending on the severity of the bug.
|
||||
|
||||
The decision on whether backporting a change will be performed is done at the discretion of the
|
||||
|
@ -220,12 +219,11 @@ This decision is made on a case-by-case basis.
|
|||
Check [our release posts](https://about.gitlab.com/releases/categories/releases/).
|
||||
|
||||
Each month, we publish either a major or minor release of GitLab. At the end
|
||||
of those release posts there are three sections to look for: Deprecations, Removals, and Important notes on upgrading. These will include:
|
||||
of those release posts, there are three sections to look for: Deprecations, Removals, and Important notes on upgrading. These will include:
|
||||
|
||||
- Steps you need to perform as part of an upgrade.
|
||||
For example [8.12](https://about.gitlab.com/releases/2016/09/22/gitlab-8-12-released/#upgrade-barometer)
|
||||
required the Elasticsearch index to be recreated. Any older version of GitLab upgrading to 8.12 or higher
|
||||
would require this.
|
||||
required the Elasticsearch index to be recreated. Any older version of GitLab upgrading to 8.12 or higher would require this.
|
||||
- Changes to the versions of software we support such as
|
||||
[ceasing support for IE11 in GitLab 13](https://about.gitlab.com/releases/2020/03/22/gitlab-12-9-released/#ending-support-for-internet-explorer-11).
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ similarly mitigated by a rate limit.
|
|||
- [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
|
||||
- [Raw endpoints rate limits](../user/admin_area/settings/rate_limits_on_raw_endpoints.md).
|
||||
- [Protected paths](../user/admin_area/settings/protected_paths.md).
|
||||
- [Import/Export rate limits](../user/admin_area/settings/import_export_rate_limits.md).
|
||||
|
||||
## Rack Attack initializer
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
type: reference
|
||||
stage: Manage
|
||||
group: Import
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Project/Group Import/Export rate limits
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35728) in GitLab 13.2.
|
||||
|
||||
The following table includes configurable rate limits. The following table includes limits on a
|
||||
per minute per user basis:
|
||||
|
||||
| Limit | Default (per minute per user) |
|
||||
|--------------------------|-------------------------------|
|
||||
| Project Import | 6 |
|
||||
| Project Export | 6 |
|
||||
| Project Export Download | 1 |
|
||||
| Group Import | 6 |
|
||||
| Group Export | 6 |
|
||||
| Group Export Download | 1 |
|
||||
|
||||
All rate limits are:
|
||||
|
||||
- Configurable at **(admin)** **Admin Area > Settings > Network > Import/Export Rate Limits**
|
||||
- Applied per minute per user
|
||||
- Not applied per IP address
|
||||
- Active by default. To disable, set the option to `0`
|
||||
- Logged to `auth.log` file if exceed rate limit
|
||||
|
||||
![Import/Export rate limits](img/import_export_rate_limits_v13_2.png)
|
|
@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
This setting allows you to rate limit the requests to the issue creation endpoint.
|
||||
It defaults to 300 requests per minute.
|
||||
You can change it in **Admin Area > Settings > Network > Performance Optimization**.
|
||||
You can change it in **Admin Area > Settings > Network > Issues Rate Limits**.
|
||||
|
||||
For example, requests using the
|
||||
[Projects::IssuesController#create](https://gitlab.com/gitlab-org/gitlab/raw/master/app/controllers/projects/issues_controller.rb)
|
||||
|
|
|
@ -125,10 +125,10 @@ The repository will push soon. To force a push, click the appropriate button.
|
|||
|
||||
## Setting up a push mirror to another GitLab instance with 2FA activated
|
||||
|
||||
1. On the destination GitLab instance, create a [personal access token](../../profile/personal_access_tokens.md) with `API` scope.
|
||||
1. On the destination GitLab instance, create a [personal access token](../../profile/personal_access_tokens.md) with `write_repository` scope.
|
||||
1. On the source GitLab instance:
|
||||
1. Fill in the **Git repository URL** field using this format: `https://oauth2@<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
|
||||
1. Fill in **Password** field with the GitLab personal access token created on the destination GitLab instance.
|
||||
1. Fill in the **Password** field with the GitLab personal access token created on the destination GitLab instance.
|
||||
1. Click the **Mirror repository** button.
|
||||
|
||||
## Pulling from a remote repository **(STARTER)**
|
||||
|
|
|
@ -82,7 +82,9 @@ module.exports = path => {
|
|||
'^.+\\.js$': 'babel-jest',
|
||||
'^.+\\.vue$': 'vue-jest',
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui|bootstrap-vue|three|monaco-editor)/)'],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(@gitlab/ui|bootstrap-vue|three|monaco-editor|monaco-yaml)/)',
|
||||
],
|
||||
timers: 'fake',
|
||||
testEnvironment: '<rootDir>/spec/frontend/environment.js',
|
||||
testEnvironmentOptions: {
|
||||
|
|
|
@ -19,17 +19,17 @@ module Gitlab
|
|||
# and only do that when it's needed.
|
||||
def rate_limits
|
||||
{
|
||||
issues_create: { threshold: -> { Gitlab::CurrentSettings.current_application_settings.issues_create_limit }, interval: 1.minute },
|
||||
project_export: { threshold: 30, interval: 5.minutes },
|
||||
project_download_export: { threshold: 10, interval: 10.minutes },
|
||||
issues_create: { threshold: -> { application_settings.issues_create_limit }, interval: 1.minute },
|
||||
project_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute },
|
||||
project_download_export: { threshold: -> { application_settings.project_download_export_limit }, interval: 1.minute },
|
||||
project_repositories_archive: { threshold: 5, interval: 1.minute },
|
||||
project_generate_new_export: { threshold: 30, interval: 5.minutes },
|
||||
project_import: { threshold: 30, interval: 5.minutes },
|
||||
play_pipeline_schedule: { threshold: 1, interval: 1.minute },
|
||||
show_raw_controller: { threshold: -> { Gitlab::CurrentSettings.current_application_settings.raw_blob_request_limit }, interval: 1.minute },
|
||||
group_export: { threshold: 30, interval: 5.minutes },
|
||||
group_download_export: { threshold: 10, interval: 10.minutes },
|
||||
group_import: { threshold: 30, interval: 5.minutes }
|
||||
project_generate_new_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute },
|
||||
project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute },
|
||||
play_pipeline_schedule: { threshold: 1, interval: 1.minute },
|
||||
show_raw_controller: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute },
|
||||
group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute },
|
||||
group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute },
|
||||
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute }
|
||||
}.freeze
|
||||
end
|
||||
|
||||
|
@ -130,6 +130,10 @@ module Gitlab
|
|||
|
||||
"application_rate_limiter:#{serialized}"
|
||||
end
|
||||
|
||||
def application_settings
|
||||
Gitlab::CurrentSettings.current_application_settings
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Style/Documentation
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class DigestColumn
|
||||
class PersonalAccessToken < ActiveRecord::Base
|
||||
self.table_name = 'personal_access_tokens'
|
||||
end
|
||||
|
||||
def perform(model, attribute_from, attribute_to, start_id, stop_id)
|
||||
model = model.constantize if model.is_a?(String)
|
||||
|
||||
model.transaction do
|
||||
relation = model.where(id: start_id..stop_id).where.not(attribute_from => nil).lock
|
||||
|
||||
relation.each do |instance|
|
||||
instance.update_columns(attribute_to => Gitlab::CryptoHelper.sha256(instance.read_attribute(attribute_from)),
|
||||
attribute_from => nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,104 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# EncryptColumn migrates data from an unencrypted column - `foo`, say - to
|
||||
# an encrypted column - `encrypted_foo`, say.
|
||||
#
|
||||
# To avoid depending on a particular version of the model in app/, add a
|
||||
# model to `lib/gitlab/background_migration/models/encrypt_columns` and use
|
||||
# it in the migration that enqueues the jobs, so code can be shared.
|
||||
#
|
||||
# For this background migration to work, the table that is migrated _has_ to
|
||||
# have an `id` column as the primary key. Additionally, the encrypted column
|
||||
# should be managed by attr_encrypted, and map to an attribute with the same
|
||||
# name as the unencrypted column (i.e., the unencrypted column should be
|
||||
# shadowed), unless you want to define specific methods / accessors in the
|
||||
# temporary model in `/models/encrypt_columns/your_model.rb`.
|
||||
#
|
||||
class EncryptColumns
|
||||
def perform(model, attributes, from, to)
|
||||
model = model.constantize if model.is_a?(String)
|
||||
|
||||
# If sidekiq hasn't undergone a restart, its idea of what columns are
|
||||
# present may be inaccurate, so ensure this is as fresh as possible
|
||||
model.reset_column_information
|
||||
model.define_attribute_methods
|
||||
|
||||
attributes = expand_attributes(model, Array(attributes).map(&:to_sym))
|
||||
|
||||
model.transaction do
|
||||
# Use SELECT ... FOR UPDATE to prevent the value being changed while
|
||||
# we are encrypting it
|
||||
relation = model.where(id: from..to).lock
|
||||
|
||||
relation.each do |instance|
|
||||
encrypt!(instance, attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def clear_migrated_values?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Build a hash of { attribute => encrypted column name }
|
||||
def expand_attributes(klass, attributes)
|
||||
expanded = attributes.flat_map do |attribute|
|
||||
attr_config = klass.encrypted_attributes[attribute]
|
||||
crypt_column_name = attr_config&.fetch(:attribute)
|
||||
|
||||
raise "Couldn't determine encrypted column for #{klass}##{attribute}" if
|
||||
crypt_column_name.nil?
|
||||
|
||||
raise "#{klass} source column: #{attribute} is missing" unless
|
||||
klass.column_names.include?(attribute.to_s)
|
||||
|
||||
# Running the migration without the destination column being present
|
||||
# leads to data loss
|
||||
raise "#{klass} destination column: #{crypt_column_name} is missing" unless
|
||||
klass.column_names.include?(crypt_column_name.to_s)
|
||||
|
||||
[attribute, crypt_column_name]
|
||||
end
|
||||
|
||||
Hash[*expanded]
|
||||
end
|
||||
|
||||
# Generate ciphertext for each column and update the database
|
||||
def encrypt!(instance, attributes)
|
||||
to_clear = attributes
|
||||
.map { |plain, crypt| apply_attribute!(instance, plain, crypt) }
|
||||
.compact
|
||||
.flat_map { |plain| [plain, nil] }
|
||||
|
||||
to_clear = Hash[*to_clear]
|
||||
|
||||
if instance.changed?
|
||||
instance.save!
|
||||
|
||||
if clear_migrated_values?
|
||||
instance.update_columns(to_clear)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def apply_attribute!(instance, plain_column, crypt_column)
|
||||
plaintext = instance[plain_column]
|
||||
ciphertext = instance[crypt_column]
|
||||
|
||||
# No need to do anything if the plaintext is nil, or an encrypted
|
||||
# value already exists
|
||||
return unless plaintext.present?
|
||||
return if ciphertext.present?
|
||||
|
||||
# attr_encrypted will calculate and set the expected value for us
|
||||
instance.public_send("#{plain_column}=", plaintext) # rubocop:disable GitlabSecurity/PublicSend
|
||||
|
||||
plain_column
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,32 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# EncryptColumn migrates data from an unencrypted column - `foo`, say - to
|
||||
# an encrypted column - `encrypted_foo`, say.
|
||||
#
|
||||
# We only create a subclass here because we want to isolate this migration
|
||||
# (migrating unencrypted runner registration tokens to encrypted columns)
|
||||
# from other `EncryptColumns` migration. This class name is going to be
|
||||
# serialized and stored in Redis and later picked by Sidekiq, so we need to
|
||||
# create a separate class name in order to isolate these migration tasks.
|
||||
#
|
||||
# We can solve this differently, see tech debt issue:
|
||||
#
|
||||
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54328
|
||||
#
|
||||
class EncryptRunnersTokens < EncryptColumns
|
||||
def perform(model, from, to)
|
||||
resource = "::Gitlab::BackgroundMigration::Models::EncryptColumns::#{model.to_s.capitalize}"
|
||||
model = resource.constantize
|
||||
attributes = model.encrypted_attributes.keys
|
||||
|
||||
super(model, attributes, from, to)
|
||||
end
|
||||
|
||||
def clear_migrated_values?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
module Models
|
||||
module EncryptColumns
|
||||
# This model is shared between synchronous and background migrations to
|
||||
# encrypt the `runners_token` column in `namespaces` table.
|
||||
#
|
||||
class Namespace < ActiveRecord::Base
|
||||
include ::EachBatch
|
||||
|
||||
self.table_name = 'namespaces'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
def runners_token=(value)
|
||||
self.runners_token_encrypted =
|
||||
::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
|
||||
end
|
||||
|
||||
def self.encrypted_attributes
|
||||
{ runners_token: { attribute: :runners_token_encrypted } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
module Models
|
||||
module EncryptColumns
|
||||
# This model is shared between synchronous and background migrations to
|
||||
# encrypt the `runners_token` column in `projects` table.
|
||||
#
|
||||
class Project < ActiveRecord::Base
|
||||
include ::EachBatch
|
||||
|
||||
self.table_name = 'projects'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
def runners_token=(value)
|
||||
self.runners_token_encrypted =
|
||||
::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
|
||||
end
|
||||
|
||||
def self.encrypted_attributes
|
||||
{ runners_token: { attribute: :runners_token_encrypted } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
module Models
|
||||
module EncryptColumns
|
||||
# This model is shared between synchronous and background migrations to
|
||||
# encrypt the `token` column in `ci_runners` table.
|
||||
#
|
||||
class Runner < ActiveRecord::Base
|
||||
include ::EachBatch
|
||||
|
||||
self.table_name = 'ci_runners'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
def token=(value)
|
||||
self.token_encrypted =
|
||||
::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
|
||||
end
|
||||
|
||||
def self.encrypted_attributes
|
||||
{ token: { attribute: :token_encrypted } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
module Models
|
||||
module EncryptColumns
|
||||
# This model is shared between synchronous and background migrations to
|
||||
# encrypt the `runners_token` column in `application_settings` table.
|
||||
#
|
||||
class Settings < ActiveRecord::Base
|
||||
include ::EachBatch
|
||||
include ::CacheableAttributes
|
||||
|
||||
self.table_name = 'application_settings'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
after_commit do
|
||||
::ApplicationSetting.expire
|
||||
end
|
||||
|
||||
def runners_registration_token=(value)
|
||||
self.runners_registration_token_encrypted =
|
||||
::Gitlab::CryptoHelper.aes256_gcm_encrypt(value)
|
||||
end
|
||||
|
||||
def self.encrypted_attributes
|
||||
{
|
||||
runners_registration_token: {
|
||||
attribute: :runners_registration_token_encrypted
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
module Models
|
||||
module EncryptColumns
|
||||
# This model is shared between synchronous and background migrations to
|
||||
# encrypt the `token` and `url` columns
|
||||
class WebHook < ActiveRecord::Base
|
||||
include ::EachBatch
|
||||
|
||||
self.table_name = 'web_hooks'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
attr_encrypted :token,
|
||||
mode: :per_attribute_iv,
|
||||
algorithm: 'aes-256-gcm',
|
||||
key: ::Settings.attr_encrypted_db_key_base_32
|
||||
|
||||
attr_encrypted :url,
|
||||
mode: :per_attribute_iv,
|
||||
algorithm: 'aes-256-gcm',
|
||||
key: ::Settings.attr_encrypted_db_key_base_32
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
include Gitlab::Utils::StrongMemoize
|
||||
include Gitlab::Routing
|
||||
|
||||
LATEST_COMMITS_LIMIT = 10
|
||||
LATEST_COMMITS_LIMIT = 2
|
||||
|
||||
def initialize(project, commit_sha)
|
||||
@project = project
|
||||
|
|
|
@ -8,9 +8,11 @@ module Gitlab
|
|||
MAX_MSG_SIZE = 128.kilobytes.freeze
|
||||
|
||||
def self.exists?(remote_url)
|
||||
request = Gitaly::FindRemoteRepositoryRequest.new(remote: remote_url)
|
||||
storage = GitalyClient.random_storage
|
||||
|
||||
response = GitalyClient.call(GitalyClient.random_storage,
|
||||
request = Gitaly::FindRemoteRepositoryRequest.new(remote: remote_url, storage_name: storage)
|
||||
|
||||
response = GitalyClient.call(storage,
|
||||
:remote_service,
|
||||
:find_remote_repository, request,
|
||||
timeout: GitalyClient.medium_timeout)
|
||||
|
|
|
@ -82,7 +82,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def count_request
|
||||
@request_counter ||= Gitlab::Metrics.counter(:redis_client_requests_total, 'Client side Redis request count, per Redis server')
|
||||
@request_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_requests_total, 'Client side Redis request count, per Redis server')
|
||||
@request_counter.increment({ storage: storage_key })
|
||||
end
|
||||
|
||||
|
@ -90,7 +90,7 @@ module Gitlab
|
|||
# This metric is meant to give a client side view of how the Redis
|
||||
# server is doing. Redis itself does not expose error counts. This
|
||||
# metric can be used for Redis alerting and service health monitoring.
|
||||
@exception_counter ||= Gitlab::Metrics.counter(:redis_client_exceptions_total, 'Client side Redis exception count, per Redis server, per exception class')
|
||||
@exception_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_exceptions_total, 'Client side Redis exception count, per Redis server, per exception class')
|
||||
@exception_counter.increment({ storage: storage_key, exception: ex.class.to_s })
|
||||
end
|
||||
|
||||
|
|
|
@ -93,8 +93,14 @@ module Quality
|
|||
|
||||
private
|
||||
|
||||
def migration_and_background_migration_folders
|
||||
TEST_LEVEL_FOLDERS.fetch(:migration) + TEST_LEVEL_FOLDERS.fetch(:background_migration)
|
||||
end
|
||||
|
||||
def folders_pattern(level)
|
||||
case level
|
||||
when :migration
|
||||
"{#{migration_and_background_migration_folders.join(',')}}"
|
||||
# Geo specs aren't in a specific folder, but they all have the :geo tag, so we must search for them globally
|
||||
when :all, :geo
|
||||
'**'
|
||||
|
@ -105,6 +111,8 @@ module Quality
|
|||
|
||||
def folders_regex(level)
|
||||
case level
|
||||
when :migration
|
||||
"(#{migration_and_background_migration_folders.join('|')})"
|
||||
# Geo specs aren't in a specific folder, but they all have the :geo tag, so we must search for them globally
|
||||
when :all, :geo
|
||||
''
|
||||
|
|
|
@ -192,6 +192,11 @@ msgid_plural "%d issues selected"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d issue successfully imported with the label"
|
||||
msgid_plural "%d issues successfully imported with the label"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d layer"
|
||||
msgid_plural "%d layers"
|
||||
msgstr[0] ""
|
||||
|
@ -6082,6 +6087,9 @@ msgstr ""
|
|||
msgid "Configure limit for issues created per minute by web and API requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "Configure limits for Project/Group Import/Export."
|
||||
msgstr ""
|
||||
|
||||
msgid "Configure limits for web and API requests."
|
||||
msgstr ""
|
||||
|
||||
|
@ -12226,6 +12234,9 @@ msgstr ""
|
|||
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import/Export Rate Limits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import/Export illustration"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12756,9 +12767,6 @@ msgstr ""
|
|||
msgid "Issues referenced by merge requests and commits within the default branch will be closed automatically"
|
||||
msgstr ""
|
||||
|
||||
msgid "Issues successfully imported with the label"
|
||||
msgstr ""
|
||||
|
||||
msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13989,6 +13997,24 @@ msgstr ""
|
|||
msgid "Maven Metadata"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max Group Export Download requests per minute per user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max Group Export requests per minute per user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max Group Import requests per minute per user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max Project Export Download requests per minute per user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max Project Export requests per minute per user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max Project Import requests per minute per user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max access level"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19505,6 +19531,9 @@ msgstr ""
|
|||
msgid "Required approvals (%{approvals_given} given, you've approved)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Required in this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Requirement"
|
||||
msgstr ""
|
||||
|
||||
|
@ -22379,9 +22408,6 @@ msgstr ""
|
|||
msgid "Suggestions must all be on the same branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Suggestions that change line count can't be added to batches, yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Suggestions:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23397,7 +23423,7 @@ msgstr ""
|
|||
msgid "This also resolves all related threads"
|
||||
msgstr ""
|
||||
|
||||
msgid "This also resolves the discussion"
|
||||
msgid "This also resolves this thread"
|
||||
msgstr ""
|
||||
|
||||
msgid "This application was created by %{link_to_owner}."
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"jquery.caret": "^0.3.1",
|
||||
"jquery.waitforimages": "^2.2.0",
|
||||
"js-cookie": "^2.2.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"jszip": "^3.1.3",
|
||||
"jszip-utils": "^0.0.2",
|
||||
"katex": "^0.10.0",
|
||||
|
@ -105,8 +106,9 @@
|
|||
"mermaid": "^8.5.2",
|
||||
"mersenne-twister": "1.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"monaco-editor": "^0.18.1",
|
||||
"monaco-editor-webpack-plugin": "^1.7.0",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||
"monaco-yaml": "^2.4.0",
|
||||
"mousetrap": "^1.4.6",
|
||||
"pdfjs-dist": "^2.0.943",
|
||||
"pikaday": "^1.8.0",
|
||||
|
@ -220,7 +222,7 @@
|
|||
},
|
||||
"resolutions": {
|
||||
"chokidar": "^3.4.0",
|
||||
"monaco-editor": "0.18.1",
|
||||
"monaco-editor": "0.20.0",
|
||||
"vue-jest/ts-jest": "24.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
@ -38,7 +38,7 @@ module QA
|
|||
|
||||
# Note: This test doesn't have the :orchestrated tag because it runs in the Test::Integration::Praefect
|
||||
# scenario with other tests that aren't considered orchestrated.
|
||||
context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect do
|
||||
context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/227127', type: :investigating } do
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'repo-storage-move'
|
||||
|
|
|
@ -941,7 +941,7 @@ RSpec.describe GroupsController do
|
|||
|
||||
allow(Gitlab::ApplicationRateLimiter)
|
||||
.to receive(:increment)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold] + 1)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold].call + 1)
|
||||
end
|
||||
|
||||
it 'throttles the endpoint' do
|
||||
|
@ -1015,7 +1015,7 @@ RSpec.describe GroupsController do
|
|||
|
||||
allow(Gitlab::ApplicationRateLimiter)
|
||||
.to receive(:increment)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold] + 1)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold].call + 1)
|
||||
end
|
||||
|
||||
it 'throttles the endpoint' do
|
||||
|
|
|
@ -1197,7 +1197,7 @@ RSpec.describe ProjectsController do
|
|||
before do
|
||||
allow(Gitlab::ApplicationRateLimiter)
|
||||
.to receive(:increment)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits["project_#{action}".to_sym][:threshold] + 1)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits["project_#{action}".to_sym][:threshold].call + 1)
|
||||
end
|
||||
|
||||
it 'prevents requesting project export' do
|
||||
|
@ -1264,7 +1264,7 @@ RSpec.describe ProjectsController do
|
|||
before do
|
||||
allow(Gitlab::ApplicationRateLimiter)
|
||||
.to receive(:increment)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold] + 1)
|
||||
.and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold].call + 1)
|
||||
end
|
||||
|
||||
it 'prevents requesting project export' do
|
||||
|
|
|
@ -8,9 +8,11 @@ import 'monaco-editor/esm/vs/language/css/monaco.contribution';
|
|||
import 'monaco-editor/esm/vs/language/json/monaco.contribution';
|
||||
import 'monaco-editor/esm/vs/language/html/monaco.contribution';
|
||||
import 'monaco-editor/esm/vs/basic-languages/monaco.contribution';
|
||||
import 'monaco-yaml/esm/monaco.contribution';
|
||||
|
||||
// This language starts trying to spin up web workers which obviously breaks in Jest environment
|
||||
jest.mock('monaco-editor/esm/vs/language/typescript/tsMode');
|
||||
jest.mock('monaco-yaml/esm/yamlMode');
|
||||
|
||||
export * from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
export default global.monaco;
|
||||
|
|
|
@ -637,6 +637,8 @@ describe('RepoEditor', () => {
|
|||
// set cursor to line 2, column 1
|
||||
vm.editor.instance.setSelection(new Range(2, 1, 2, 1));
|
||||
vm.editor.instance.focus();
|
||||
|
||||
jest.spyOn(vm.editor.instance, 'hasTextFocus').mockReturnValue(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -199,6 +199,14 @@ describe('Multi-file editor library', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('schemas', () => {
|
||||
it('registers custom schemas defined with Monaco', () => {
|
||||
expect(monacoLanguages.yaml.yamlDefaults.diagnosticsOptions).toMatchObject({
|
||||
schemas: [{ fileMatch: ['*.gitlab-ci.yml'] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceSelectedText', () => {
|
||||
let model;
|
||||
let editor;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
isTextFile,
|
||||
registerLanguages,
|
||||
registerSchemas,
|
||||
trimPathComponents,
|
||||
insertFinalNewline,
|
||||
trimTrailingWhitespace,
|
||||
|
@ -158,6 +159,57 @@ describe('WebIDE utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('registerSchemas', () => {
|
||||
let options;
|
||||
|
||||
beforeEach(() => {
|
||||
options = {
|
||||
validate: true,
|
||||
enableSchemaRequest: true,
|
||||
hover: true,
|
||||
completion: true,
|
||||
schemas: [
|
||||
{
|
||||
uri: 'http://myserver/foo-schema.json',
|
||||
fileMatch: ['*'],
|
||||
schema: {
|
||||
id: 'http://myserver/foo-schema.json',
|
||||
type: 'object',
|
||||
properties: {
|
||||
p1: { enum: ['v1', 'v2'] },
|
||||
p2: { $ref: 'http://myserver/bar-schema.json' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
uri: 'http://myserver/bar-schema.json',
|
||||
schema: {
|
||||
id: 'http://myserver/bar-schema.json',
|
||||
type: 'object',
|
||||
properties: { q1: { enum: ['x1', 'x2'] } },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
jest.spyOn(languages.json.jsonDefaults, 'setDiagnosticsOptions');
|
||||
jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions');
|
||||
});
|
||||
|
||||
it.each`
|
||||
language | defaultsObj
|
||||
${'json'} | ${languages.json.jsonDefaults}
|
||||
${'yaml'} | ${languages.yaml.yamlDefaults}
|
||||
`(
|
||||
'registers the given schemas with monaco for lang: $language',
|
||||
({ language, defaultsObj }) => {
|
||||
registerSchemas({ language, options });
|
||||
|
||||
expect(defaultsObj.setDiagnosticsOptions).toHaveBeenCalledWith(options);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('trimTrailingWhitespace', () => {
|
||||
it.each`
|
||||
input | output
|
||||
|
|
|
@ -33,6 +33,7 @@ describe('IssuableListRootApp', () => {
|
|||
isFinishedAlertShowing,
|
||||
isInProgressAlertShowing,
|
||||
jiraImport: {
|
||||
importedIssuesCount: 1,
|
||||
isInProgress,
|
||||
isFinished,
|
||||
label,
|
||||
|
@ -77,7 +78,7 @@ describe('IssuableListRootApp', () => {
|
|||
|
||||
describe('shows an alert', () => {
|
||||
it('tells the user the Jira import has finished', () => {
|
||||
expect(findAlert().text()).toBe('Issues successfully imported with the label');
|
||||
expect(findAlert().text()).toBe('1 issue successfully imported with the label');
|
||||
});
|
||||
|
||||
it('contains the label title associated with the Jira import', () => {
|
||||
|
|
|
@ -19,6 +19,8 @@ describe('Squash before merge component', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findLabel = () => wrapper.find('[data-testid="squashLabel"]');
|
||||
|
||||
describe('checkbox', () => {
|
||||
const findCheckbox = () => wrapper.find('.js-squash-checkbox');
|
||||
|
||||
|
@ -64,8 +66,6 @@ describe('Squash before merge component', () => {
|
|||
});
|
||||
|
||||
describe('label', () => {
|
||||
const findLabel = () => wrapper.find('[data-testid="squashLabel"]');
|
||||
|
||||
describe.each`
|
||||
isDisabled | expectation
|
||||
${true} | ${'grays out text if it is true'}
|
||||
|
@ -84,6 +84,27 @@ describe('Squash before merge component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('tooltip', () => {
|
||||
const tooltipTitle = () => findLabel().element.dataset.title;
|
||||
|
||||
it('does not render when isDisabled is false', () => {
|
||||
createComponent({
|
||||
value: true,
|
||||
isDisabled: false,
|
||||
});
|
||||
expect(tooltipTitle()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('display message when when isDisabled is true', () => {
|
||||
createComponent({
|
||||
value: true,
|
||||
isDisabled: true,
|
||||
});
|
||||
|
||||
expect(tooltipTitle()).toBe('Required in this project.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('about link', () => {
|
||||
it('is not rendered if no help path is passed', () => {
|
||||
createComponent({
|
||||
|
|
|
@ -71,7 +71,7 @@ describe('Suggestion Diff component', () => {
|
|||
|
||||
it('renders correct tooltip message for apply button', () => {
|
||||
createComponent();
|
||||
expect(wrapper.vm.tooltipMessage).toBe('This also resolves the discussion');
|
||||
expect(wrapper.vm.tooltipMessage).toBe('This also resolves this thread');
|
||||
});
|
||||
|
||||
describe('when apply suggestion is clicked', () => {
|
||||
|
@ -227,11 +227,10 @@ describe('Suggestion Diff component', () => {
|
|||
createComponent({ canApply: false });
|
||||
});
|
||||
|
||||
it('disables apply suggestion and add to batch buttons', () => {
|
||||
it('disables apply suggestion and hides add to batch button', () => {
|
||||
expect(findApplyButton().exists()).toBe(true);
|
||||
expect(findAddToBatchButton().exists()).toBe(true);
|
||||
expect(findAddToBatchButton().exists()).toBe(false);
|
||||
expect(findApplyButton().attributes('disabled')).toBe('true');
|
||||
expect(findAddToBatchButton().attributes('disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('renders correct tooltip message for apply button', () => {
|
||||
|
|
|
@ -6,25 +6,6 @@ const buildMockTextNode = literal => {
|
|||
};
|
||||
};
|
||||
|
||||
const buildMockTextNodeWithAdjacentInlineCode = isForward => {
|
||||
const direction = isForward ? 'next' : 'prev';
|
||||
const literalOpen = '[';
|
||||
const literalClose = ' file]: https://file.com/file.md';
|
||||
return {
|
||||
literal: isForward ? literalOpen : literalClose,
|
||||
type: 'text',
|
||||
[direction]: {
|
||||
literal: 'raw',
|
||||
tickCount: 1,
|
||||
type: 'code',
|
||||
[direction]: {
|
||||
literal: isForward ? literalClose : literalOpen,
|
||||
[direction]: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const buildMockListNode = literal => {
|
||||
return {
|
||||
firstChild: {
|
||||
|
@ -38,15 +19,23 @@ const buildMockListNode = literal => {
|
|||
};
|
||||
};
|
||||
|
||||
export const buildMockParagraphNode = literal => {
|
||||
return {
|
||||
firstChild: buildMockTextNode(literal),
|
||||
type: 'paragraph',
|
||||
};
|
||||
};
|
||||
|
||||
export const kramdownListNode = buildMockListNode('TOC');
|
||||
export const normalListNode = buildMockListNode('Just another bullet point');
|
||||
|
||||
export const kramdownTextNode = buildMockTextNode('{:toc}');
|
||||
export const identifierTextNode = buildMockTextNode('[Some text]: https://link.com');
|
||||
export const identifierInlineCodeTextEnteringNode = buildMockTextNodeWithAdjacentInlineCode(true);
|
||||
export const identifierInlineCodeTextExitingNode = buildMockTextNodeWithAdjacentInlineCode(false);
|
||||
export const embeddedRubyTextNode = buildMockTextNode('<%= partial("some/path") %>');
|
||||
export const normalTextNode = buildMockTextNode('This is just normal text.');
|
||||
export const normalParagraphNode = buildMockParagraphNode(
|
||||
'This is just normal paragraph. It has multiple sentences.',
|
||||
);
|
||||
|
||||
const uneditableOpenToken = {
|
||||
type: 'openTag',
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph';
|
||||
import {
|
||||
buildUneditableOpenTokens,
|
||||
buildUneditableCloseToken,
|
||||
} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
|
||||
|
||||
import { buildMockParagraphNode, normalParagraphNode } from '../../mock_data';
|
||||
|
||||
const identifierParagraphNode = buildMockParagraphNode(
|
||||
`[another-identifier]: https://example.com "This example has a title" [identifier]: http://example1.com [this link]: http://example2.com`,
|
||||
);
|
||||
|
||||
describe('Render Identifier Paragraph renderer', () => {
|
||||
describe('canRender', () => {
|
||||
it.each`
|
||||
node | paragraph | target
|
||||
${identifierParagraphNode} | ${'[Some text]: https://link.com'} | ${true}
|
||||
${normalParagraphNode} | ${'Normal non-identifier text. Another sentence.'} | ${false}
|
||||
`(
|
||||
'should return $target when the $node matches $paragraph syntax',
|
||||
({ node, paragraph, target }) => {
|
||||
const context = {
|
||||
entering: true,
|
||||
getChildrenText: jest.fn().mockReturnValueOnce(paragraph),
|
||||
};
|
||||
|
||||
expect(renderer.canRender(node, context)).toBe(target);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('render', () => {
|
||||
let origin;
|
||||
|
||||
beforeEach(() => {
|
||||
origin = jest.fn();
|
||||
});
|
||||
|
||||
it('should return uneditable open tokens when entering', () => {
|
||||
const context = { entering: true, origin };
|
||||
|
||||
expect(renderer.render(identifierParagraphNode, context)).toStrictEqual(
|
||||
buildUneditableOpenTokens(origin()),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an uneditable close tokens when exiting', () => {
|
||||
const context = { entering: false, origin };
|
||||
|
||||
expect(renderer.render(identifierParagraphNode, context)).toStrictEqual(
|
||||
buildUneditableCloseToken(origin()),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,61 +0,0 @@
|
|||
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_text';
|
||||
import {
|
||||
buildUneditableOpenTokens,
|
||||
buildUneditableCloseTokens,
|
||||
buildUneditableTokens,
|
||||
} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
|
||||
|
||||
import {
|
||||
identifierTextNode,
|
||||
identifierInlineCodeTextEnteringNode,
|
||||
identifierInlineCodeTextExitingNode,
|
||||
normalTextNode,
|
||||
} from '../../mock_data';
|
||||
|
||||
describe('Render Identifier Text renderer', () => {
|
||||
describe('canRender', () => {
|
||||
it('should return true when the argument `literal` has identifier syntax', () => {
|
||||
expect(renderer.canRender(identifierTextNode)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when the argument `literal` has identifier syntax and forward adjacent inline code', () => {
|
||||
expect(renderer.canRender(identifierInlineCodeTextEnteringNode)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when the argument `literal` has identifier syntax and backward adjacent inline code', () => {
|
||||
expect(renderer.canRender(identifierInlineCodeTextExitingNode)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when the argument `literal` lacks identifier syntax', () => {
|
||||
expect(renderer.canRender(normalTextNode)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('render', () => {
|
||||
const origin = jest.fn();
|
||||
|
||||
it('should return uneditable tokens for basic identifier syntax', () => {
|
||||
const context = { origin };
|
||||
|
||||
expect(renderer.render(identifierTextNode, context)).toStrictEqual(
|
||||
buildUneditableTokens(origin()),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return uneditable open tokens for non-basic inline code identifier syntax when entering', () => {
|
||||
const context = { origin };
|
||||
|
||||
expect(renderer.render(identifierInlineCodeTextEnteringNode, context)).toStrictEqual(
|
||||
buildUneditableOpenTokens(origin()),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return uneditable close tokens for non-basic inline code identifier syntax when exiting', () => {
|
||||
const context = { origin };
|
||||
|
||||
expect(renderer.render(identifierInlineCodeTextExitingNode, context)).toStrictEqual(
|
||||
buildUneditableCloseTokens(origin()),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, schema: 20180105212544 do
|
||||
RSpec.describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount do
|
||||
let(:namespaces_table) { table(:namespaces) }
|
||||
let(:projects_table) { table(:projects) }
|
||||
let(:merge_requests_table) { table(:merge_requests) }
|
||||
let(:merge_request_diffs_table) { table(:merge_request_diffs) }
|
||||
let(:merge_request_diff_commits_table) { table(:merge_request_diff_commits) }
|
||||
|
||||
let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
|
||||
let(:namespace) { namespaces_table.create!(name: 'gitlab-org', path: 'gitlab-org') }
|
||||
let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: namespace.id) }
|
||||
let(:merge_request) do
|
||||
merge_requests_table.create!(target_project_id: project.id,
|
||||
target_branch: 'master',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue