Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d75ac09b4a
commit
93fb07b8c9
|
@ -791,3 +791,6 @@ Style/ClassAndModuleChildren:
|
|||
|
||||
Fips/OpenSSL:
|
||||
Enabled: false
|
||||
|
||||
Gemspec/AvoidExecutingGit:
|
||||
Enabled: false
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import Tracking from '~/tracking';
|
||||
|
||||
function addBlobLinksTracking(containerSelector, eventsToTrack) {
|
||||
const containerEl = document.querySelector(containerSelector);
|
||||
|
||||
if (!containerEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventName = 'click_link';
|
||||
const label = 'file_line_action';
|
||||
|
||||
containerEl.addEventListener('click', (e) => {
|
||||
eventsToTrack.forEach((event) => {
|
||||
if (e.target.matches(event.selector)) {
|
||||
Tracking.event(undefined, eventName, {
|
||||
label,
|
||||
property: event.property,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default addBlobLinksTracking;
|
|
@ -6,6 +6,7 @@ import Code from './code';
|
|||
import CodeBlockHighlight from './code_block_highlight';
|
||||
import FootnoteReference from './footnote_reference';
|
||||
import FootnoteDefinition from './footnote_definition';
|
||||
import Frontmatter from './frontmatter';
|
||||
import Heading from './heading';
|
||||
import HardBreak from './hard_break';
|
||||
import HorizontalRule from './horizontal_rule';
|
||||
|
@ -37,6 +38,7 @@ export default Extension.create({
|
|||
CodeBlockHighlight.name,
|
||||
FootnoteReference.name,
|
||||
FootnoteDefinition.name,
|
||||
Frontmatter.name,
|
||||
HardBreak.name,
|
||||
Heading.name,
|
||||
HorizontalRule.name,
|
||||
|
|
|
@ -155,7 +155,7 @@ const defaultSerializerConfig = {
|
|||
},
|
||||
inline: true,
|
||||
}),
|
||||
[Frontmatter.name]: (state, node) => {
|
||||
[Frontmatter.name]: preserveUnchanged((state, node) => {
|
||||
const { language } = node.attrs;
|
||||
const syntax = {
|
||||
toml: '+++',
|
||||
|
@ -168,7 +168,7 @@ const defaultSerializerConfig = {
|
|||
state.ensureNewLine();
|
||||
state.write(syntax);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
}),
|
||||
[Figure.name]: renderHTMLNode('figure'),
|
||||
[FigureCaption.name]: renderHTMLNode('figcaption'),
|
||||
[HardBreak.name]: preserveUnchanged(renderHardBreak),
|
||||
|
|
|
@ -185,6 +185,14 @@ const factorySpecs = {
|
|||
identifier: hastNode.properties.identifier,
|
||||
}),
|
||||
},
|
||||
|
||||
frontmatter: {
|
||||
type: 'block',
|
||||
selector: 'frontmatter',
|
||||
getAttrs: (hastNode) => ({
|
||||
language: hastNode.properties.language,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const SANITIZE_ALLOWLIST = ['level', 'identifier', 'numeric', 'language', 'url', 'isReference'];
|
||||
|
@ -239,6 +247,9 @@ export default () => {
|
|||
'definition',
|
||||
'linkReference',
|
||||
'imageReference',
|
||||
'yaml',
|
||||
'toml',
|
||||
'json',
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -2,10 +2,14 @@ import { pick } from 'lodash';
|
|||
import normalize from 'mdurl/encode';
|
||||
import { unified } from 'unified';
|
||||
import remarkParse from 'remark-parse';
|
||||
import remarkFrontmatter from 'remark-frontmatter';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkRehype, { all } from 'remark-rehype';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
|
||||
const skipFrontmatterHandler = (language) => (h, node) =>
|
||||
h(node.position, 'frontmatter', { language }, [{ type: 'text', value: node.value }]);
|
||||
|
||||
const skipRenderingHandlers = {
|
||||
footnoteReference: (h, node) =>
|
||||
h(node.position, 'footnoteReference', { identifier: node.identifier, label: node.label }, []),
|
||||
|
@ -61,12 +65,16 @@ const skipRenderingHandlers = {
|
|||
all(h, node),
|
||||
);
|
||||
},
|
||||
toml: skipFrontmatterHandler('toml'),
|
||||
yaml: skipFrontmatterHandler('yaml'),
|
||||
json: skipFrontmatterHandler('json'),
|
||||
};
|
||||
|
||||
const createParser = ({ skipRendering = [] }) => {
|
||||
return unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkFrontmatter, ['yaml', 'toml', { type: 'json', marker: ';' }])
|
||||
.use(remarkRehype, {
|
||||
allowDangerousHtml: true,
|
||||
handlers: {
|
||||
|
|
|
@ -4,6 +4,7 @@ import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
|
|||
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
|
||||
import LineHighlighter from '~/blob/line_highlighter';
|
||||
import initBlobBundle from '~/blob_edit/blob_bundle';
|
||||
import addBlobLinksTracking from '~/blob/blob_links_tracking';
|
||||
|
||||
export default () => {
|
||||
new LineHighlighter(); // eslint-disable-line no-new
|
||||
|
@ -11,10 +12,16 @@ export default () => {
|
|||
// eslint-disable-next-line no-new
|
||||
new BlobLinePermalinkUpdater(
|
||||
document.querySelector('#blob-content-holder'),
|
||||
'.diff-line-num[data-line-number], .diff-line-num[data-line-number] *',
|
||||
'.file-line-num[data-line-number], .file-line-num[data-line-number] *',
|
||||
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
|
||||
);
|
||||
|
||||
const eventsToTrack = [
|
||||
{ selector: '.file-line-blame', property: 'blame' },
|
||||
{ selector: '.file-line-num', property: 'link' },
|
||||
];
|
||||
addBlobLinksTracking('#blob-content-holder', eventsToTrack);
|
||||
|
||||
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
|
||||
const fileBlobPermalinkUrl =
|
||||
fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
|
||||
|
|
|
@ -46,6 +46,9 @@ export default {
|
|||
const { name, status } = this.group;
|
||||
return `${name} - ${status.label}`;
|
||||
},
|
||||
jobGroupClasses() {
|
||||
return [this.cssClassJobName, `job-${this.group.status.group}`];
|
||||
},
|
||||
},
|
||||
errorCaptured(err, _vm, info) {
|
||||
reportToSentry('job_group_dropdown', `error: ${err}, info: ${info}`);
|
||||
|
@ -68,7 +71,7 @@ export default {
|
|||
type="button"
|
||||
data-toggle="dropdown"
|
||||
data-display="static"
|
||||
:class="cssClassJobName"
|
||||
:class="jobGroupClasses"
|
||||
class="dropdown-menu-toggle gl-pipeline-job-width! gl-pr-4!"
|
||||
>
|
||||
<div class="gl-display-flex gl-align-items-stretch gl-justify-content-space-between">
|
||||
|
|
|
@ -200,6 +200,9 @@ export default {
|
|||
},
|
||||
{ 'gl-rounded-lg': this.isBridge },
|
||||
this.cssClassJobName,
|
||||
{
|
||||
[`job-${this.status.group}`]: this.isSingleItem,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
|
|
@ -27,6 +27,7 @@ query getBlobInfo(
|
|||
fileType
|
||||
language
|
||||
path
|
||||
blamePath
|
||||
editBlobPath
|
||||
gitpodBlobUrl
|
||||
ideEditPath
|
||||
|
|
|
@ -51,6 +51,10 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
blamePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
lines() {
|
||||
|
@ -76,6 +80,7 @@ export default {
|
|||
:number="startingFrom + index + 1"
|
||||
:content="line"
|
||||
:language="language"
|
||||
:blame-path="blamePath"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="gl-display-flex">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlSafeHtmlDirective } from '@gitlab/ui';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { setAttributes } from '~/lib/utils/dom_utils';
|
||||
import { BIDI_CHARS, BIDI_CHARS_CLASS_LIST, BIDI_CHAR_TOOLTIP } from '../constants';
|
||||
|
||||
|
@ -7,6 +8,7 @@ export default {
|
|||
directives: {
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
number: {
|
||||
type: Number,
|
||||
|
@ -20,6 +22,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
blamePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
formattedContent() {
|
||||
|
@ -33,9 +39,6 @@ export default {
|
|||
|
||||
return content;
|
||||
},
|
||||
firstLineClass() {
|
||||
return { 'gl-mt-3!': this.number === 1 };
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
wrapBidiChar(bidiChar) {
|
||||
|
@ -56,22 +59,26 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="gl-display-flex">
|
||||
<div class="gl-p-0! gl-absolute gl-z-index-3 gl-border-r diff-line-num line-numbers">
|
||||
<div
|
||||
class="gl-p-0! gl-absolute gl-z-index-3 diff-line-num gl-border-r gl-display-flex line-links line-numbers"
|
||||
>
|
||||
<a
|
||||
v-if="glFeatures.fileLineBlame"
|
||||
class="gl-user-select-none gl-shadow-none! file-line-blame"
|
||||
:href="`${blamePath}#L${number}`"
|
||||
></a>
|
||||
<a
|
||||
:id="`L${number}`"
|
||||
class="gl-user-select-none gl-ml-5 gl-pr-3 gl-shadow-none! file-line-num diff-line-num"
|
||||
:class="firstLineClass"
|
||||
class="gl-user-select-none gl-shadow-none! file-line-num"
|
||||
:href="`#L${number}`"
|
||||
:data-line-number="number"
|
||||
data-testid="line-number-anchor"
|
||||
>
|
||||
{{ number }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<pre
|
||||
class="gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11! gl-border-none! code highlight gl-line-height-normal"
|
||||
:class="firstLineClass"
|
||||
class="gl-p-0! gl-w-full gl-overflow-visible! gl-border-none! code highlight gl-line-height-normal"
|
||||
><code><span :id="`LC${number}`" v-safe-html="formattedContent" :lang="language" class="line" data-testid="content"></span></code></pre>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -199,6 +199,7 @@ export default {
|
|||
:starting-from="firstChunk.startingFrom"
|
||||
:is-highlighted="firstChunk.isHighlighted"
|
||||
:language="firstChunk.language"
|
||||
:blame-path="blob.blamePath"
|
||||
/>
|
||||
|
||||
<gl-loading-icon v-if="isLoading" size="sm" class="gl-my-5" />
|
||||
|
@ -213,6 +214,7 @@ export default {
|
|||
:is-highlighted="chunk.isHighlighted"
|
||||
:chunk-index="index"
|
||||
:language="chunk.language"
|
||||
:blame-path="blob.blamePath"
|
||||
@appear="highlightChunk"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -75,6 +75,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
canInviteMembers: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -321,7 +326,7 @@ export default {
|
|||
<rect width="280" height="20" x="10" y="130" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
</template>
|
||||
<template #dropdown-footer>
|
||||
<template v-if="canInviteMembers" #dropdown-footer>
|
||||
<gl-dropdown-divider />
|
||||
<gl-dropdown-item @click="closeDropdown">
|
||||
<invite-members-trigger
|
||||
|
|
|
@ -319,6 +319,7 @@ export default {
|
|||
:assignees="workItemAssignees.assignees.nodes"
|
||||
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
|
||||
:work-item-type="workItemType"
|
||||
:can-invite-members="workItemAssignees.canInviteMembers"
|
||||
@error="error = $event"
|
||||
/>
|
||||
<work-item-labels
|
||||
|
|
|
@ -24,6 +24,7 @@ fragment WorkItem on WorkItem {
|
|||
... on WorkItemWidgetAssignees {
|
||||
type
|
||||
allowsMultipleAssignees
|
||||
canInviteMembers
|
||||
assignees {
|
||||
nodes {
|
||||
...User
|
||||
|
|
|
@ -202,6 +202,10 @@
|
|||
float: none;
|
||||
border-left: 1px solid $gray-100;
|
||||
|
||||
.file-line-num {
|
||||
@include gl-min-w-9;
|
||||
}
|
||||
|
||||
i {
|
||||
float: none;
|
||||
margin-right: 0;
|
||||
|
|
|
@ -49,8 +49,9 @@
|
|||
|
||||
a {
|
||||
font-family: $monospace-font;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
@include gl-display-flex;
|
||||
@include gl-justify-content-end;
|
||||
|
||||
i,
|
||||
svg {
|
||||
|
@ -91,3 +92,55 @@ td.line-numbers {
|
|||
cursor: pointer;
|
||||
text-decoration: underline wavy $red-500;
|
||||
}
|
||||
|
||||
.blob-viewer {
|
||||
.line-numbers {
|
||||
// for server-side-rendering
|
||||
.line-links {
|
||||
@include gl-display-flex;
|
||||
|
||||
|
||||
&:first-child {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// for client
|
||||
&.line-links {
|
||||
min-width: 6rem;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
+ pre {
|
||||
margin-left: 6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line-links {
|
||||
&:hover a::before,
|
||||
&:focus-within a::before {
|
||||
@include gl-visibility-visible;
|
||||
}
|
||||
}
|
||||
|
||||
.file-line-num {
|
||||
min-width: 4.5rem;
|
||||
@include gl-justify-content-end;
|
||||
@include gl-flex-grow-1;
|
||||
@include gl-pr-3;
|
||||
}
|
||||
|
||||
.file-line-blame {
|
||||
@include gl-ml-3;
|
||||
}
|
||||
|
||||
.file-line-num,
|
||||
.file-line-blame {
|
||||
@include gl-align-items-center;
|
||||
@include gl-display-flex;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,32 +98,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin line-number-link($color) {
|
||||
min-width: $gl-spacing-scale-9;
|
||||
|
||||
@mixin line-link($color, $icon) {
|
||||
&::before {
|
||||
@include gl-display-none;
|
||||
@include gl-visibility-hidden;
|
||||
@include gl-align-self-center;
|
||||
@include gl-mt-2;
|
||||
@include gl-mr-2;
|
||||
@include gl-w-4;
|
||||
@include gl-h-4;
|
||||
@include gl-absolute;
|
||||
@include gl-left-3;
|
||||
background-color: $color;
|
||||
mask-image: asset_url('icons-stacked.svg#link');
|
||||
@include gl-mr-1;
|
||||
@include gl-w-5;
|
||||
@include gl-h-5;
|
||||
background-color: rgba($color, 0.3);
|
||||
mask-image: asset_url('icons-stacked.svg##{$icon}');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
@include gl-display-inline-block;
|
||||
&:hover {
|
||||
&::before {
|
||||
background-color: rgba($color, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus::before {
|
||||
@include gl-display-inline-block;
|
||||
@mixin line-hover-bg($color: $white-normal) {
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
background-color: darken($color, 10);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -127,7 +127,15 @@ $dark-il: #de935f;
|
|||
.code.dark {
|
||||
// Line numbers
|
||||
.file-line-num {
|
||||
@include line-number-link($dark-line-num-color);
|
||||
@include line-link($white, 'link');
|
||||
}
|
||||
|
||||
.file-line-blame {
|
||||
@include line-link($white, 'git');
|
||||
}
|
||||
|
||||
.line-links {
|
||||
@include line-hover-bg($dark-main-bg);
|
||||
}
|
||||
|
||||
.line-numbers,
|
||||
|
|
|
@ -120,7 +120,15 @@ $monokai-gh: #75715e;
|
|||
|
||||
// Line numbers
|
||||
.file-line-num {
|
||||
@include line-number-link($monokai-line-num-color);
|
||||
@include line-link($white, 'link');
|
||||
}
|
||||
|
||||
.file-line-blame {
|
||||
@include line-link($white, 'git');
|
||||
}
|
||||
|
||||
.line-links {
|
||||
@include line-hover-bg($monokai-bg);
|
||||
}
|
||||
|
||||
.line-numbers,
|
||||
|
|
|
@ -25,7 +25,15 @@
|
|||
|
||||
// Line numbers
|
||||
.file-line-num {
|
||||
@include line-number-link($black-transparent);
|
||||
@include line-link($black, 'link');
|
||||
}
|
||||
|
||||
.file-line-blame {
|
||||
@include line-link($black, 'git');
|
||||
}
|
||||
|
||||
.line-links {
|
||||
@include line-hover-bg;
|
||||
}
|
||||
|
||||
.line-numbers,
|
||||
|
|
|
@ -123,7 +123,15 @@ $solarized-dark-il: #2aa198;
|
|||
|
||||
// Line numbers
|
||||
.file-line-num {
|
||||
@include line-number-link($solarized-dark-line-color);
|
||||
@include line-link($white, 'link');
|
||||
}
|
||||
|
||||
.file-line-blame {
|
||||
@include line-link($white, 'git');
|
||||
}
|
||||
|
||||
.line-links {
|
||||
@include line-hover-bg($solarized-dark-pre-bg);
|
||||
}
|
||||
|
||||
.line-numbers,
|
||||
|
|
|
@ -109,7 +109,15 @@ $solarized-light-il: #2aa198;
|
|||
@include hljs-override('title.class_.inherited__', $solarized-light-no);
|
||||
// Line numbers
|
||||
.file-line-num {
|
||||
@include line-number-link($solarized-light-line-color);
|
||||
@include line-link($black, 'link');
|
||||
}
|
||||
|
||||
.file-line-blame {
|
||||
@include line-link($black, 'git');
|
||||
}
|
||||
|
||||
.line-links {
|
||||
@include line-hover-bg($solarized-light-pre-bg);
|
||||
}
|
||||
|
||||
.line-numbers,
|
||||
|
|
|
@ -95,7 +95,15 @@ $white-gc-bg: #eaf2f5;
|
|||
|
||||
// Line numbers
|
||||
.file-line-num {
|
||||
@include line-number-link($black-transparent);
|
||||
@include line-link($black, 'link');
|
||||
}
|
||||
|
||||
.file-line-blame {
|
||||
@include line-link($black, 'git');
|
||||
}
|
||||
|
||||
.line-links {
|
||||
@include line-hover-bg;
|
||||
}
|
||||
|
||||
.line-numbers,
|
||||
|
|
|
@ -228,3 +228,17 @@
|
|||
.progress-bar.bg-primary {
|
||||
background-color: var(--blue-500, $blue-500) !important;
|
||||
}
|
||||
|
||||
.ci-job-component {
|
||||
.job-failed {
|
||||
background-color: var(--red-50, $red-50);
|
||||
}
|
||||
}
|
||||
|
||||
.gl-dark {
|
||||
.ci-job-component {
|
||||
.job-failed {
|
||||
background-color: var(--gray-200, $gray-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:highlight_js, @project)
|
||||
push_frontend_feature_flag(:file_line_blame, @project)
|
||||
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
|
||||
end
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ class Projects::TreeController < Projects::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:lazy_load_commits, @project)
|
||||
push_frontend_feature_flag(:highlight_js, @project)
|
||||
push_frontend_feature_flag(:file_line_blame, @project)
|
||||
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
|
||||
end
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:lazy_load_commits, @project)
|
||||
push_frontend_feature_flag(:highlight_js, @project)
|
||||
push_frontend_feature_flag(:file_line_blame, @project)
|
||||
push_frontend_feature_flag(:increase_page_size_exponentially, @project)
|
||||
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
|
||||
push_licensed_feature(:security_orchestration_policies) if @project.present? && @project.licensed_feature_available?(:security_orchestration_policies)
|
||||
|
|
|
@ -61,10 +61,14 @@ module Types
|
|||
Types::TimeTracking::TimelogCategoryType.connection_type,
|
||||
null: true,
|
||||
description: "Timelog categories for the namespace.",
|
||||
_deprecated_feature_flag: :timelog_categories
|
||||
alpha: { milestone: '15.3' }
|
||||
|
||||
markdown_field :description_html, null: true
|
||||
|
||||
def timelog_categories
|
||||
object.timelog_categories if Feature.enabled?(:timelog_categories)
|
||||
end
|
||||
|
||||
def cross_project_pipeline_available?
|
||||
object.licensed_feature_available?(:cross_project_pipelines)
|
||||
end
|
||||
|
|
|
@ -442,14 +442,14 @@ module Types
|
|||
Types::TimeTracking::TimelogCategoryType.connection_type,
|
||||
null: true,
|
||||
description: "Timelog categories for the project.",
|
||||
_deprecated_feature_flag: :timelog_categories
|
||||
alpha: { milestone: '15.3' }
|
||||
|
||||
field :fork_targets, Types::NamespaceType.connection_type,
|
||||
resolver: Resolvers::Projects::ForkTargetsResolver,
|
||||
description: 'Namespaces in which the current user can fork the project into.'
|
||||
|
||||
def timelog_categories
|
||||
object.project_namespace.timelog_categories
|
||||
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
|
||||
end
|
||||
|
||||
def label(title:)
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
class Approval < ApplicationRecord
|
||||
include CreatedAtFilterable
|
||||
include Importable
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :merge_request
|
||||
|
||||
validates :merge_request_id, presence: true
|
||||
validates :merge_request_id, presence: true, unless: :importing?
|
||||
validates :user_id, presence: true, uniqueness: { scope: [:merge_request_id] }
|
||||
|
||||
scope :with_user, -> { joins(:user) }
|
||||
|
|
|
@ -10,8 +10,6 @@ class LooseForeignKeys::DeletedRecord < Gitlab::Database::SharedModel
|
|||
|
||||
partitioned_by :partition, strategy: :sliding_list,
|
||||
next_partition_if: -> (active_partition) do
|
||||
return false if Feature.disabled?(:lfk_automatic_partition_creation)
|
||||
|
||||
oldest_record_in_partition = LooseForeignKeys::DeletedRecord
|
||||
.select(:id, :created_at)
|
||||
.for_partition(active_partition)
|
||||
|
@ -23,8 +21,6 @@ class LooseForeignKeys::DeletedRecord < Gitlab::Database::SharedModel
|
|||
oldest_record_in_partition.created_at < PARTITION_DURATION.ago
|
||||
end,
|
||||
detach_partition_if: -> (partition) do
|
||||
return false if Feature.disabled?(:lfk_automatic_partition_dropping)
|
||||
|
||||
!LooseForeignKeys::DeletedRecord
|
||||
.for_partition(partition)
|
||||
.status_pending
|
||||
|
|
|
@ -45,7 +45,7 @@ module Issues
|
|||
# current_user (defined in BaseService) is not available within run_after_commit block
|
||||
user = current_user
|
||||
issue.run_after_commit do
|
||||
NewIssueWorker.perform_async(issue.id, user.id)
|
||||
NewIssueWorker.perform_async(issue.id, user.id, issue.class.to_s)
|
||||
Issues::PlacementWorker.perform_async(nil, issue.project_id)
|
||||
Namespaces::OnboardingIssueCreatedWorker.perform_async(issue.project.namespace_id)
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- title = capture do
|
||||
= html_escape(_('This commit was signed with a verified signature, but the committer email is %{strong_open}not verified%{strong_close} to belong to the same user.')) % { strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
|
||||
= html_escape(_('This commit was signed with a verified signature, but the committer email is not associated with the GPG Key.'))
|
||||
|
||||
- locals = { signature: signature, title: title, label: _('Unverified'), css_class: ['invalid'], icon: 'status_notfound_borderless', show_user: true }
|
||||
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
#blob-content.file-content.code.js-syntax-highlight
|
||||
- offset = defined?(first_line_number) ? first_line_number : 1
|
||||
.line-numbers
|
||||
.line-numbers{ class: "gl-p-0\!" }
|
||||
- if blob.data.present?
|
||||
- link = blob_link if defined?(blob_link)
|
||||
- blame_link = project_blame_path(@project, tree_join(@ref, blob.path))
|
||||
- blob.data.each_line.each_with_index do |_, index|
|
||||
- i = index + offset
|
||||
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
|
||||
%a.file-line-num.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
|
||||
= i
|
||||
.line-links.diff-line-num
|
||||
- if Feature.enabled?(:file_line_blame)
|
||||
%a.file-line-blame{ href: "#{blame_link}#L#{i}" }
|
||||
%a.file-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
|
||||
= i
|
||||
- highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil
|
||||
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
|
||||
%pre.code.highlight
|
||||
|
|
|
@ -13,7 +13,11 @@ class NewIssueWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
worker_resource_boundary :cpu
|
||||
weight 2
|
||||
|
||||
def perform(issue_id, user_id)
|
||||
attr_reader :issuable_class
|
||||
|
||||
def perform(issue_id, user_id, issuable_class = 'Issue')
|
||||
@issuable_class = issuable_class.constantize
|
||||
|
||||
return unless objects_found?(issue_id, user_id)
|
||||
|
||||
::EventCreateService.new.open_issue(issuable, user)
|
||||
|
@ -25,8 +29,4 @@ class NewIssueWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
.new(project: issuable.project, current_user: user)
|
||||
.execute(issuable)
|
||||
end
|
||||
|
||||
def issuable_class
|
||||
Issue
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: file_line_blame
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92538
|
||||
rollout_issue_url:
|
||||
milestone: '15.3'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: lfk_automatic_partition_creation
|
||||
introduced_by_url:
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346907
|
||||
milestone: '14.6'
|
||||
type: development
|
||||
group: group::sharding
|
||||
default_enabled: true
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: lfk_automatic_partition_dropping
|
||||
introduced_by_url:
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346908
|
||||
milestone: '14.6'
|
||||
type: development
|
||||
group: group::sharding
|
||||
default_enabled: true
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: rate_limit_gitlab_shell_by_ip
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91599
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367998
|
||||
milestone: '15.3'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
|
@ -12183,7 +12183,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="groupstoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
|
||||
| <a id="groupsubgroupcreationlevel"></a>`subgroupCreationLevel` | [`String`](#string) | Permission level required to create subgroups within the group. |
|
||||
| <a id="grouptemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
|
||||
| <a id="grouptimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the namespace. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
|
||||
| <a id="grouptimelogcategories"></a>`timelogCategories` **{warning-solid}** | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Timelog categories for the namespace. |
|
||||
| <a id="grouptotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
|
||||
| <a id="grouptotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
|
||||
| <a id="grouptwofactorgraceperiod"></a>`twoFactorGracePeriod` | [`Int`](#int) | Time before two-factor authentication is enforced. |
|
||||
|
@ -14768,7 +14768,7 @@ Contains statistics about a milestone.
|
|||
| <a id="namespacesharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
|
||||
| <a id="namespacestoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
|
||||
| <a id="namespacetemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
|
||||
| <a id="namespacetimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the namespace. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
|
||||
| <a id="namespacetimelogcategories"></a>`timelogCategories` **{warning-solid}** | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Timelog categories for the namespace. |
|
||||
| <a id="namespacetotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
|
||||
| <a id="namespacetotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
|
||||
| <a id="namespacevisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
|
||||
|
@ -15536,7 +15536,7 @@ Represents vulnerability finding of a security report on the pipeline.
|
|||
| <a id="projectsuggestioncommitmessage"></a>`suggestionCommitMessage` | [`String`](#string) | Commit message used to apply merge request suggestions. |
|
||||
| <a id="projecttaglist"></a>`tagList` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.12. Use `topics`. |
|
||||
| <a id="projectterraformstates"></a>`terraformStates` | [`TerraformStateConnection`](#terraformstateconnection) | Terraform states associated with the project. (see [Connections](#connections)) |
|
||||
| <a id="projecttimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the project. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
|
||||
| <a id="projecttimelogcategories"></a>`timelogCategories` **{warning-solid}** | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Timelog categories for the project. |
|
||||
| <a id="projecttopics"></a>`topics` | [`[String!]`](#string) | List of project topics. |
|
||||
| <a id="projectuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. |
|
||||
| <a id="projectvisibility"></a>`visibility` | [`String`](#string) | Visibility of the project. |
|
||||
|
|
|
@ -60,7 +60,7 @@ See the [Rails guides](https://guides.rubyonrails.org/action_mailer_basics.html#
|
|||
# The email address including the %{key} placeholder that will be replaced to reference the
|
||||
# item being replied to. This %{key} should be included in its entirety within the email
|
||||
# address and not replaced by another value.
|
||||
# For example: emailadress+%{key}@gmail.com.
|
||||
# For example: emailaddress+%{key}@gmail.com.
|
||||
# The placeholder must appear in the "user" part of the address (before the `@`). It can be omitted but some features,
|
||||
# including Service Desk, may not work properly.
|
||||
address: "gitlab-incoming+%{key}@gmail.com"
|
||||
|
|
|
@ -8,21 +8,37 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
> Moved to GitLab Premium in 13.9.
|
||||
|
||||
Code Owners define who owns specific files or directories in a repository.
|
||||
Code Owners define who develops and maintains a feature, and own the resulting
|
||||
files or directories in a repository.
|
||||
|
||||
- The users you define as Code Owners are displayed in the UI when you browse directories.
|
||||
- You can set your merge requests so they must be approved by Code Owners before merge.
|
||||
- You can protect a branch and allow only Code Owners to approve changes to the branch.
|
||||
|
||||
If you don't want to use Code Owners for approvals, you can
|
||||
[configure rules](merge_requests/approvals/rules.md) instead.
|
||||
Use Code Owners and approvers together with
|
||||
[approval rules](merge_requests/approvals/rules.md) to build a flexible approval
|
||||
workflow:
|
||||
|
||||
- Use **Code Owners** to define the users who have domain expertise for specific paths in your repository.
|
||||
- Use **Approvers** and **Approval rules** to define domains of expertise (such as a security team)
|
||||
that are not scoped to specific file paths in your repository.
|
||||
- **Approvers** define the users.
|
||||
- **Approval rules** define when these users can approve work, and whether or not their approval is required.
|
||||
|
||||
For example:
|
||||
|
||||
| Type | Name | Scope | Comment |
|
||||
|------|------|--------|------------|
|
||||
| Approval rule | UX | All files | A user experience (UX) team member reviews the user experience of all changes made in your project. |
|
||||
| Approval rule | Security | All files | A security team member reviews all changes for vulnerabilities. |
|
||||
| Code Owner approval rule | Frontend: Code Style | `*.css` files | A frontend engineer reviews CSS file changes for adherence to project style standards. |
|
||||
| Code Owner approval rule | Backend: Code Review | `*.rb` files | A backend engineer reviews the logic and code style of Ruby files. |
|
||||
|
||||
## Set up Code Owners
|
||||
|
||||
You can use Code Owners to specify users or [shared groups](members/share_project_with_groups.md)
|
||||
that are responsible for specific files and directories in a repository.
|
||||
|
||||
To set up Code Owners:
|
||||
Create a `CODEOWNERS` file to specify users or [shared groups](members/share_project_with_groups.md)
|
||||
that are responsible for specific files and directories in a repository. Each repository
|
||||
can have a single `CODEOWNERS` file. To create it:
|
||||
|
||||
1. Choose the location where you want to specify Code Owners:
|
||||
- In the root directory of the repository
|
||||
|
|
|
@ -7,7 +7,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Merge request approval rules **(PREMIUM)**
|
||||
|
||||
Approval rules define how many [approvals](index.md) a merge request must receive before it can
|
||||
be merged, and which users should do the approving. You can define approval rules:
|
||||
be merged, and which users should do the approving. They can be used in conjunction
|
||||
with [Code owners](#code-owners-as-eligible-approvers) to ensure that changes are
|
||||
reviewed both by the group maintaining the feature, and any groups responsible
|
||||
for specific areas of oversight.
|
||||
|
||||
You can define approval rules:
|
||||
|
||||
- [As project defaults](#add-an-approval-rule).
|
||||
- [Per merge request](#edit-or-override-merge-request-approval-rules).
|
||||
|
|
|
@ -2027,3 +2027,18 @@
|
|||
07_02__gitlab_specific_markdown__task_list_items__004:
|
||||
spec_txt_example_position: 678
|
||||
source_specification: gitlab
|
||||
07_03__gitlab_specific_markdown__front_matter__001:
|
||||
spec_txt_example_position: 679
|
||||
source_specification: gitlab
|
||||
07_03__gitlab_specific_markdown__front_matter__002:
|
||||
spec_txt_example_position: 680
|
||||
source_specification: gitlab
|
||||
07_03__gitlab_specific_markdown__front_matter__003:
|
||||
spec_txt_example_position: 681
|
||||
source_specification: gitlab
|
||||
07_03__gitlab_specific_markdown__front_matter__004:
|
||||
spec_txt_example_position: 682
|
||||
source_specification: gitlab
|
||||
07_03__gitlab_specific_markdown__front_matter__005:
|
||||
spec_txt_example_position: 683
|
||||
source_specification: gitlab
|
||||
|
|
|
@ -813,8 +813,7 @@
|
|||
<a id="user-content-bar" class="anchor" href="#bar" aria-hidden="true"></a>Bar</h2>
|
||||
<p data-sourcepos="6:1-6:3" dir="auto">Baz</p>
|
||||
wysiwyg: |-
|
||||
<hr>
|
||||
<h2>Foo</h2>
|
||||
<pre language="yaml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>Foo</code></pre>
|
||||
<h2>Bar</h2>
|
||||
<p>Baz</p>
|
||||
04_03__leaf_blocks__setext_headings__018:
|
||||
|
@ -834,8 +833,7 @@
|
|||
<copy-code></copy-code>
|
||||
</div>
|
||||
wysiwyg: |-
|
||||
<hr>
|
||||
<hr>
|
||||
<pre language="yaml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code></code></pre>
|
||||
04_03__leaf_blocks__setext_headings__020:
|
||||
canonical: |
|
||||
<ul>
|
||||
|
@ -7745,3 +7743,76 @@
|
|||
<p data-sourcepos="3:3-3:20">text in loose list</p>
|
||||
</li>
|
||||
</ul>
|
||||
07_03__gitlab_specific_markdown__front_matter__001:
|
||||
canonical: |
|
||||
<pre>
|
||||
<code>
|
||||
title: YAML front matter
|
||||
</code>
|
||||
</pre>
|
||||
static: |-
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-yaml" lang="yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">YAML front matter</span></span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
wysiwyg: |-
|
||||
<pre language="yaml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>title: YAML front matter</code></pre>
|
||||
07_03__gitlab_specific_markdown__front_matter__002:
|
||||
canonical: |
|
||||
<pre>
|
||||
<code>
|
||||
title: TOML front matter
|
||||
</code>
|
||||
</pre>
|
||||
static: |-
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-toml" lang="toml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="toml"><span class="err">title:</span> <span class="err">TOML</span> <span class="err">front</span> <span class="err">matter</span></span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
wysiwyg: |-
|
||||
<pre language="toml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>title: TOML front matter</code></pre>
|
||||
07_03__gitlab_specific_markdown__front_matter__003:
|
||||
canonical: |
|
||||
<pre>
|
||||
<code>
|
||||
{
|
||||
"title": "JSON front matter"
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
static: |-
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="1:1-5:3" class="code highlight js-syntax-highlight language-json" lang="json" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="json"><span class="p">{</span></span>
|
||||
<span id="LC2" class="line" lang="json"><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JSON front matter"</span></span>
|
||||
<span id="LC3" class="line" lang="json"><span class="p">}</span></span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
wysiwyg: |-
|
||||
<pre language="json" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>{
|
||||
"title": "JSON front matter"
|
||||
}</code></pre>
|
||||
07_03__gitlab_specific_markdown__front_matter__004:
|
||||
canonical: |
|
||||
<p>text</p>
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:4" dir="auto">text</p>
|
||||
<hr data-sourcepos="3:1-3:3">
|
||||
<h2 data-sourcepos="4:1-5:3" dir="auto">
|
||||
<a id="user-content-title-yaml-front-matter" class="anchor" href="#title-yaml-front-matter" aria-hidden="true"></a>title: YAML front matter</h2>
|
||||
wysiwyg: |-
|
||||
<p>text</p>
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
07_03__gitlab_specific_markdown__front_matter__005:
|
||||
canonical: |
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
static: |-
|
||||
<hr data-sourcepos="1:2-1:4">
|
||||
<h2 data-sourcepos="2:1-3:3" dir="auto">
|
||||
<a id="user-content-title-yaml-front-matter" class="anchor" href="#title-yaml-front-matter" aria-hidden="true"></a>title: YAML front matter</h2>
|
||||
wysiwyg: |-
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
|
|
|
@ -2203,3 +2203,27 @@
|
|||
- [~] inapplicable
|
||||
|
||||
text in loose list
|
||||
07_03__gitlab_specific_markdown__front_matter__001: |
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
07_03__gitlab_specific_markdown__front_matter__002: |
|
||||
+++
|
||||
title: TOML front matter
|
||||
+++
|
||||
07_03__gitlab_specific_markdown__front_matter__003: |
|
||||
;;;
|
||||
{
|
||||
"title": "JSON front matter"
|
||||
}
|
||||
;;;
|
||||
07_03__gitlab_specific_markdown__front_matter__004: |
|
||||
text
|
||||
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
07_03__gitlab_specific_markdown__front_matter__005: |2
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
|
|
|
@ -1702,12 +1702,11 @@
|
|||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "horizontalRule"
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"type": "frontmatter",
|
||||
"attrs": {
|
||||
"level": 2
|
||||
"language": "yaml",
|
||||
"class": "code highlight",
|
||||
"isFrontmatter": true
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
|
@ -1759,10 +1758,12 @@
|
|||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "horizontalRule"
|
||||
},
|
||||
{
|
||||
"type": "horizontalRule"
|
||||
"type": "frontmatter",
|
||||
"attrs": {
|
||||
"language": "yaml",
|
||||
"class": "code highlight",
|
||||
"isFrontmatter": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -20670,3 +20671,114 @@
|
|||
Inapplicable task list items not yet implemented for WYSYWIG
|
||||
07_02__gitlab_specific_markdown__task_list_items__004: |-
|
||||
Inapplicable task list items not yet implemented for WYSYWIG
|
||||
07_03__gitlab_specific_markdown__front_matter__001: |-
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "frontmatter",
|
||||
"attrs": {
|
||||
"language": "yaml",
|
||||
"class": "code highlight",
|
||||
"isFrontmatter": true
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "title: YAML front matter"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
07_03__gitlab_specific_markdown__front_matter__002: |-
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "frontmatter",
|
||||
"attrs": {
|
||||
"language": "toml",
|
||||
"class": "code highlight",
|
||||
"isFrontmatter": true
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "title: TOML front matter"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
07_03__gitlab_specific_markdown__front_matter__003: |-
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "frontmatter",
|
||||
"attrs": {
|
||||
"language": "json",
|
||||
"class": "code highlight",
|
||||
"isFrontmatter": true
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\n \"title\": \"JSON front matter\"\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
07_03__gitlab_specific_markdown__front_matter__004: |-
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontalRule"
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"attrs": {
|
||||
"level": 2
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "title: YAML front matter"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
07_03__gitlab_specific_markdown__front_matter__005: |-
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "horizontalRule"
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"attrs": {
|
||||
"level": 2
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "title: YAML front matter"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -120,3 +120,82 @@ text in loose list
|
|||
</li>
|
||||
</ul>
|
||||
````````````````````````````````
|
||||
|
||||
## Front matter
|
||||
|
||||
See
|
||||
[Front matter](https://docs.gitlab.com/ee/user/markdown.html#front-matter) in the GitLab Flavored Markdown documentation.
|
||||
|
||||
Front matter is metadata included at the beginning of a Markdown document, preceding the content.
|
||||
This data can be used by static site generators like Jekyll, Hugo, and many other applications.
|
||||
|
||||
YAML front matter:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
.
|
||||
<pre>
|
||||
<code>
|
||||
title: YAML front matter
|
||||
</code>
|
||||
</pre>
|
||||
````````````````````````````````
|
||||
|
||||
TOML front matter:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
+++
|
||||
title: TOML front matter
|
||||
+++
|
||||
.
|
||||
<pre>
|
||||
<code>
|
||||
title: TOML front matter
|
||||
</code>
|
||||
</pre>
|
||||
````````````````````````````````
|
||||
|
||||
JSON front matter:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
;;;
|
||||
{
|
||||
"title": "JSON front matter"
|
||||
}
|
||||
;;;
|
||||
.
|
||||
<pre>
|
||||
<code>
|
||||
{
|
||||
"title": "JSON front matter"
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
````````````````````````````````
|
||||
|
||||
Front matter blocks should be inserted at the top of the document:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
text
|
||||
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
.
|
||||
<p>text</p>
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
````````````````````````````````
|
||||
|
||||
Front matter block delimiters shouldn’t be preceded by space characters:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
.
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
````````````````````````````````
|
||||
|
|
|
@ -9723,6 +9723,85 @@ text in loose list
|
|||
</ul>
|
||||
````````````````````````````````
|
||||
|
||||
## Front matter
|
||||
|
||||
See
|
||||
[Front matter](https://docs.gitlab.com/ee/user/markdown.html#front-matter) in the GitLab Flavored Markdown documentation.
|
||||
|
||||
Front matter is metadata included at the beginning of a Markdown document, preceding the content.
|
||||
This data can be used by static site generators like Jekyll, Hugo, and many other applications.
|
||||
|
||||
YAML front matter:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
.
|
||||
<pre>
|
||||
<code>
|
||||
title: YAML front matter
|
||||
</code>
|
||||
</pre>
|
||||
````````````````````````````````
|
||||
|
||||
TOML front matter:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
+++
|
||||
title: TOML front matter
|
||||
+++
|
||||
.
|
||||
<pre>
|
||||
<code>
|
||||
title: TOML front matter
|
||||
</code>
|
||||
</pre>
|
||||
````````````````````````````````
|
||||
|
||||
JSON front matter:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
;;;
|
||||
{
|
||||
"title": "JSON front matter"
|
||||
}
|
||||
;;;
|
||||
.
|
||||
<pre>
|
||||
<code>
|
||||
{
|
||||
"title": "JSON front matter"
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
````````````````````````````````
|
||||
|
||||
Front matter blocks should be inserted at the top of the document:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
text
|
||||
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
.
|
||||
<p>text</p>
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
````````````````````````````````
|
||||
|
||||
Front matter block delimiters shouldn’t be preceded by space characters:
|
||||
|
||||
```````````````````````````````` example gitlab frontmatter
|
||||
---
|
||||
title: YAML front matter
|
||||
---
|
||||
.
|
||||
<hr>
|
||||
<h2>title: YAML front matter</h2>
|
||||
````````````````````````````````
|
||||
|
||||
<!-- END TESTS -->
|
||||
|
||||
# Appendix: A parsing strategy
|
||||
|
|
|
@ -39,6 +39,7 @@ module API
|
|||
container.lfs_http_url_to_repo
|
||||
end
|
||||
|
||||
# rubocop: disable Metrics/AbcSize
|
||||
def check_allowed(params)
|
||||
# This is a separate method so that EE can alter its behaviour more
|
||||
# easily.
|
||||
|
@ -47,6 +48,14 @@ module API
|
|||
check_rate_limit!(:gitlab_shell_operation, scope: [params[:action], params[:project], actor.key_or_user])
|
||||
end
|
||||
|
||||
if Feature.enabled?(:rate_limit_gitlab_shell_by_ip, actor.user)
|
||||
rate_limiter = Gitlab::Auth::IpRateLimiter.new(request.ip)
|
||||
|
||||
unless rate_limiter.trusted_ip?
|
||||
check_rate_limit!(:gitlab_shell_operation, scope: [params[:action], params[:project], rate_limiter.ip])
|
||||
end
|
||||
end
|
||||
|
||||
# Stores some Git-specific env thread-safely
|
||||
env = parse_env
|
||||
Gitlab::Git::HookEnv.set(gl_repository, env) if container
|
||||
|
@ -101,6 +110,7 @@ module API
|
|||
response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR)
|
||||
end
|
||||
end
|
||||
# rubocop: enable Metrics/AbcSize
|
||||
|
||||
def send_git_audit_streaming_event(msg)
|
||||
# Defined in EE
|
||||
|
|
|
@ -33,6 +33,10 @@ module Gitlab
|
|||
Rack::Attack::Allow2Ban.banned?(ip)
|
||||
end
|
||||
|
||||
def trusted_ip?
|
||||
trusted_ips.any? { |netmask| netmask.include?(ip) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def skip_rate_limit?
|
||||
|
@ -47,10 +51,6 @@ module Gitlab
|
|||
Gitlab.config.rack_attack.git_basic_auth
|
||||
end
|
||||
|
||||
def trusted_ip?
|
||||
trusted_ips.any? { |netmask| netmask.include?(ip) }
|
||||
end
|
||||
|
||||
def trusted_ips
|
||||
strong_memoize(:trusted_ips) do
|
||||
config.ip_whitelist.map do |proxy|
|
||||
|
|
|
@ -129,7 +129,7 @@ module Gitlab
|
|||
# When an assignee (or any other listed association) did not exist in the members mapper, the importer is
|
||||
# assigned. We only need to assign each user once.
|
||||
def remove_duplicate_assignees
|
||||
associations = %w[issue_assignees merge_request_assignees merge_request_reviewers]
|
||||
associations = %w[issue_assignees merge_request_assignees merge_request_reviewers approvals]
|
||||
|
||||
associations.each do |association|
|
||||
next unless @relation_hash.key?(association)
|
||||
|
|
|
@ -53,6 +53,7 @@ tree:
|
|||
- project_members:
|
||||
- :user
|
||||
- merge_requests:
|
||||
- :approvals
|
||||
- :metrics
|
||||
- :award_emoji
|
||||
- :merge_request_assignees
|
||||
|
@ -122,6 +123,10 @@ included_attributes:
|
|||
- :username
|
||||
author:
|
||||
- :name
|
||||
approvals:
|
||||
- :user_id
|
||||
- :created_at
|
||||
- :updated_at
|
||||
ci_cd_settings:
|
||||
- :group_runners_enabled
|
||||
- :runner_token_expiration_interval
|
||||
|
@ -776,6 +781,9 @@ excluded_attributes:
|
|||
- :repository_size_limit
|
||||
- :external_webhook_token
|
||||
- :incident_management_issuable_escalation_statuses
|
||||
approvals:
|
||||
- :id
|
||||
- :merge_request_id
|
||||
namespaces:
|
||||
- :runners_token
|
||||
- :runners_token_encrypted
|
||||
|
|
|
@ -20964,6 +20964,9 @@ msgstr ""
|
|||
msgid "Insights"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insights|Configure a custom report for insights into your group processes such as amount of issues, bugs, and merge requests per month. %{linkStart}How do I configure an insights report?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)."
|
||||
msgstr ""
|
||||
|
||||
|
@ -39946,7 +39949,7 @@ msgstr ""
|
|||
msgid "This commit was signed with a different user's verified signature."
|
||||
msgstr ""
|
||||
|
||||
msgid "This commit was signed with a verified signature, but the committer email is %{strong_open}not verified%{strong_close} to belong to the same user."
|
||||
msgid "This commit was signed with a verified signature, but the committer email is not associated with the GPG Key."
|
||||
msgstr ""
|
||||
|
||||
msgid "This commit was signed with an %{strong_open}unverified%{strong_close} signature."
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
"raphael": "^2.2.7",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
|
|
|
@ -13,8 +13,7 @@ module QA
|
|||
:github_personal_access_token,
|
||||
:github_repository_path,
|
||||
:gitlab_repository_path,
|
||||
:personal_namespace,
|
||||
:description
|
||||
:personal_namespace
|
||||
|
||||
attr_reader :repository_storage
|
||||
|
||||
|
@ -27,7 +26,8 @@ module QA
|
|||
:template_name,
|
||||
:import,
|
||||
:import_status,
|
||||
:import_error
|
||||
:import_error,
|
||||
:description
|
||||
|
||||
attribute :group do
|
||||
Group.fabricate! do |group|
|
||||
|
@ -459,10 +459,12 @@ module QA
|
|||
|
||||
response = post(request_url(api_housekeeping_path), nil)
|
||||
|
||||
unless response.code == HTTP_STATUS_CREATED
|
||||
raise ResourceQueryError,
|
||||
"Could not perform housekeeping. Request returned (#{response.code}): `#{response.body}`."
|
||||
end
|
||||
return if response.code == HTTP_STATUS_CREATED
|
||||
|
||||
raise(
|
||||
ResourceQueryError,
|
||||
"Could not perform housekeeping. Request returned (#{response.code}): `#{response.body}`."
|
||||
)
|
||||
end
|
||||
|
||||
# Gets project statistics.
|
||||
|
|
|
@ -65,10 +65,8 @@ module QA
|
|||
end
|
||||
|
||||
def verify_repository_import
|
||||
expect(imported_project.api_response).to include(
|
||||
description: 'Project for github import test',
|
||||
import_error: nil
|
||||
)
|
||||
expect(imported_project.reload!.description).to eq('Project for github import test')
|
||||
expect(imported_project.api_response[:import_error]).to be_nil
|
||||
end
|
||||
|
||||
def verify_commits_import
|
||||
|
|
|
@ -44,10 +44,6 @@ module QA
|
|||
|
||||
it_behaves_like 'successful project creation'
|
||||
end
|
||||
|
||||
after do
|
||||
project.remove_via_api!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gemspec
|
||||
# Checks that `git` is not executed in a vendored gemspec file.
|
||||
# In some installed containers, `git` is not available.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# Gem::Specification.new do |spec|
|
||||
# spec.test_files = `git ls-files -- test/*`.split("\n")
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# Gem::Specification.new do |spec|
|
||||
# spec.name = 'your_cool_gem_name'
|
||||
# spec.test_files += Dir.glob('test/**/*')
|
||||
# end
|
||||
#
|
||||
class AvoidExecutingGit < Base
|
||||
include RangeHelp
|
||||
|
||||
MSG = 'Do not execute `git` in gemspec.'
|
||||
|
||||
# @!method gem_specification(node)
|
||||
def_node_matcher :gem_specification, <<~PATTERN
|
||||
(block
|
||||
(send
|
||||
(const
|
||||
(const {cbase nil?} :Gem) :Specification) :new)
|
||||
...)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :send_node?, <<~PATTERN
|
||||
send
|
||||
PATTERN
|
||||
|
||||
def_node_search :executes_string, <<~PATTERN
|
||||
$(xstr (str $_))
|
||||
PATTERN
|
||||
|
||||
def on_block(block_node)
|
||||
return unless gem_specification(block_node)
|
||||
|
||||
block_node.descendants.each do |node|
|
||||
next unless send_node?(node)
|
||||
|
||||
str = executes_string(node)
|
||||
|
||||
str.each do |execute_node, val|
|
||||
break unless val.start_with?('git ')
|
||||
|
||||
add_offense(execute_node, message: message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Rubocop doesn't have a good way to run excluded files without a separate invocation:
|
||||
# https://github.com/rubocop/rubocop/issues/6323
|
||||
find vendor/gems -name \*.gemspec | xargs bundle exec rubocop --only Gemspec/AvoidExecutingGit
|
|
@ -59,7 +59,8 @@ class StaticAnalysis
|
|||
Task.new(%w[yarn run block-dependencies], 1),
|
||||
Task.new(%w[yarn run check-dependencies], 1),
|
||||
Task.new(%w[scripts/lint-rugged], 1),
|
||||
Task.new(%w[scripts/gemfile_lock_changed.sh], 1)
|
||||
Task.new(%w[scripts/gemfile_lock_changed.sh], 1),
|
||||
Task.new(%w[scripts/lint-vendored-gems.sh], 1)
|
||||
].compact.freeze
|
||||
|
||||
def run_tasks!(options = {})
|
||||
|
|
|
@ -93,7 +93,7 @@ RSpec.describe 'GPG signed commits' do
|
|||
page.find('.gpg-status-box', text: 'Unverified').click
|
||||
|
||||
within '.popover' do
|
||||
expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.'
|
||||
expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not associated with the GPG Key.'
|
||||
expect(page).to have_content 'Bette Cartwright'
|
||||
expect(page).to have_content '@bette.cartwright'
|
||||
expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
|
||||
|
|
|
@ -3193,6 +3193,28 @@
|
|||
"created_at": "2020-01-10T11:21:21.235Z",
|
||||
"state": "unreviewed"
|
||||
}
|
||||
],
|
||||
"approvals": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"created_at": "2020-01-07T11:21:21.235Z",
|
||||
"updated_at": "2020-01-08T11:21:21.235Z"
|
||||
},
|
||||
{
|
||||
"user_id": 15,
|
||||
"created_at": "2020-01-07T11:21:21.235Z",
|
||||
"updated_at": "2020-01-08T11:21:21.235Z"
|
||||
},
|
||||
{
|
||||
"user_id": 16,
|
||||
"created_at": "2020-01-07T11:21:21.235Z",
|
||||
"updated_at": "2020-01-08T11:21:21.235Z"
|
||||
},
|
||||
{
|
||||
"user_id": 6,
|
||||
"created_at": "2020-01-07T11:21:21.235Z",
|
||||
"updated_at": "2020-01-08T11:21:21.235Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -3462,7 +3484,8 @@
|
|||
}
|
||||
],
|
||||
"merge_request_assignees": [],
|
||||
"merge_request_reviewers": []
|
||||
"merge_request_reviewers": [],
|
||||
"approvals": []
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,60 @@
|
|||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import addBlobLinksTracking from '~/blob/blob_links_tracking';
|
||||
import Tracking from '~/tracking';
|
||||
|
||||
describe('Blob links Tracking', () => {
|
||||
const eventName = 'click_link';
|
||||
const label = 'file_line_action';
|
||||
|
||||
const eventsToTrack = [
|
||||
{ selector: '.file-line-blame', property: 'blame' },
|
||||
{ selector: '.file-line-num', property: 'link' },
|
||||
];
|
||||
|
||||
const [blameLinkClickEvent, numLinkClickEvent] = eventsToTrack;
|
||||
|
||||
beforeEach(() => {
|
||||
setHTMLFixture(`
|
||||
<div id="blob-content-holder">
|
||||
<div class="line-links diff-line-num">
|
||||
<a href="#L5" class="file-line-blame"></a>
|
||||
<a id="L5" href="#L5" data-line-number="5" class="file-line-num">5</a>
|
||||
</div>
|
||||
<pre id="LC5">Line 5 content</pre>
|
||||
</div>
|
||||
`);
|
||||
addBlobLinksTracking('#blob-content-holder', eventsToTrack);
|
||||
jest.spyOn(Tracking, 'event');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetHTMLFixture();
|
||||
});
|
||||
|
||||
it('tracks blame link click event', () => {
|
||||
const blameButton = document.querySelector(blameLinkClickEvent.selector);
|
||||
blameButton.click();
|
||||
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, eventName, {
|
||||
label,
|
||||
property: blameLinkClickEvent.property,
|
||||
});
|
||||
});
|
||||
|
||||
it('tracks num link click event', () => {
|
||||
const numLinkButton = document.querySelector(numLinkClickEvent.selector);
|
||||
numLinkButton.click();
|
||||
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, eventName, {
|
||||
label,
|
||||
property: numLinkClickEvent.property,
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't fire tracking if the user clicks on any element that is not a link", () => {
|
||||
const codeLine = document.querySelector('#LC5');
|
||||
codeLine.click();
|
||||
|
||||
expect(Tracking.event).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -5,6 +5,7 @@ import Code from '~/content_editor/extensions/code';
|
|||
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
|
||||
import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
|
||||
import FootnoteReference from '~/content_editor/extensions/footnote_reference';
|
||||
import Frontmatter from '~/content_editor/extensions/frontmatter';
|
||||
import HardBreak from '~/content_editor/extensions/hard_break';
|
||||
import HTMLNodes from '~/content_editor/extensions/html_nodes';
|
||||
import Heading from '~/content_editor/extensions/heading';
|
||||
|
@ -38,6 +39,7 @@ const tiptapEditor = createTestEditor({
|
|||
CodeBlockHighlight,
|
||||
FootnoteDefinition,
|
||||
FootnoteReference,
|
||||
Frontmatter,
|
||||
HardBreak,
|
||||
Heading,
|
||||
HorizontalRule,
|
||||
|
@ -71,6 +73,7 @@ const {
|
|||
div,
|
||||
footnoteDefinition,
|
||||
footnoteReference,
|
||||
frontmatter,
|
||||
hardBreak,
|
||||
heading,
|
||||
horizontalRule,
|
||||
|
@ -99,6 +102,7 @@ const {
|
|||
codeBlock: { nodeType: CodeBlockHighlight.name },
|
||||
footnoteDefinition: { nodeType: FootnoteDefinition.name },
|
||||
footnoteReference: { nodeType: FootnoteReference.name },
|
||||
frontmatter: { nodeType: Frontmatter.name },
|
||||
hardBreak: { nodeType: HardBreak.name },
|
||||
heading: { nodeType: Heading.name },
|
||||
horizontalRule: { nodeType: HorizontalRule.name },
|
||||
|
@ -1190,6 +1194,45 @@ _world_.
|
|||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
---
|
||||
title: 'layout'
|
||||
---
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
frontmatter(
|
||||
{ ...source("---\ntitle: 'layout'\n---"), language: 'yaml' },
|
||||
"title: 'layout'",
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
+++
|
||||
title: 'layout'
|
||||
+++
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
frontmatter(
|
||||
{ ...source("+++\ntitle: 'layout'\n+++"), language: 'toml' },
|
||||
"title: 'layout'",
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
;;;
|
||||
{ title: 'layout' }
|
||||
;;;
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
frontmatter(
|
||||
{ ...source(";;;\n{ title: 'layout' }\n;;;"), language: 'json' },
|
||||
"{ title: 'layout' }",
|
||||
),
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const runOnly = examples.find((example) => example.only === true);
|
||||
|
|
|
@ -16,6 +16,7 @@ import FigureCaption from '~/content_editor/extensions/figure_caption';
|
|||
import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
|
||||
import FootnoteReference from '~/content_editor/extensions/footnote_reference';
|
||||
import FootnotesSection from '~/content_editor/extensions/footnotes_section';
|
||||
import Frontmatter from '~/content_editor/extensions/frontmatter';
|
||||
import HardBreak from '~/content_editor/extensions/hard_break';
|
||||
import Heading from '~/content_editor/extensions/heading';
|
||||
import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
|
||||
|
@ -52,6 +53,7 @@ const tiptapEditor = createTestEditor({
|
|||
FootnoteDefinition,
|
||||
FootnoteReference,
|
||||
FootnotesSection,
|
||||
Frontmatter,
|
||||
Figure,
|
||||
FigureCaption,
|
||||
HardBreak,
|
||||
|
|
|
@ -157,7 +157,7 @@ describe('Configure Feature Flags Modal', () => {
|
|||
beforeEach(factory.bind(null, { isRotating: true }));
|
||||
|
||||
it('should disable the project name input', async () => {
|
||||
expect(findProjectNameInput().attributes('disabled')).toBeTruthy();
|
||||
expect(findProjectNameInput().attributes('disabled')).toBe('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -230,4 +230,32 @@ console.log('Hola');
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when skipping the rendering of frontmatter types', () => {
|
||||
it.each`
|
||||
type | input
|
||||
${'yaml'} | ${'---\ntitle: page\n---'}
|
||||
${'toml'} | ${'+++\ntitle: page\n+++'}
|
||||
${'json'} | ${';;;\ntitle: page\n;;;'}
|
||||
`('transforms $type nodes into frontmatter html tags', async ({ input, type }) => {
|
||||
const result = await markdownToAST(input, [type]);
|
||||
|
||||
expectInRoot(
|
||||
result,
|
||||
expect.objectContaining({
|
||||
type: 'element',
|
||||
tagName: 'frontmatter',
|
||||
properties: {
|
||||
language: type,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
value: 'title: page',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -105,7 +105,10 @@ describe('operation settings external dashboard component', () => {
|
|||
|
||||
it('uses description text', () => {
|
||||
const description = formGroup.find('small');
|
||||
expect(description.text()).not.toBeFalsy();
|
||||
const expectedDescription =
|
||||
"Choose whether to display dashboard metrics in UTC or the user's local timezone.";
|
||||
|
||||
expect(description.text()).toBe(expectedDescription);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -138,7 +141,10 @@ describe('operation settings external dashboard component', () => {
|
|||
|
||||
it('uses description text', () => {
|
||||
const description = formGroup.find('small');
|
||||
expect(description.text()).not.toBeFalsy();
|
||||
const expectedDescription =
|
||||
'Add a button to the metrics dashboard linking directly to your existing external dashboard.';
|
||||
|
||||
expect(description.text()).toBe(expectedDescription);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -151,7 +157,6 @@ describe('operation settings external dashboard component', () => {
|
|||
});
|
||||
|
||||
it('defaults to externalDashboardUrl', () => {
|
||||
expect(input.attributes().value).toBeTruthy();
|
||||
expect(input.attributes().value).toBe(externalDashboardUrl);
|
||||
});
|
||||
|
||||
|
|
|
@ -226,7 +226,6 @@ describe('Timezone Dropdown', () => {
|
|||
it('returns the correct object if the identifier exists', () => {
|
||||
const res = findTimezoneByIdentifier(tzList, identifier);
|
||||
|
||||
expect(res).toBeTruthy();
|
||||
expect(res).toBe(tzList[2]);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ export const simpleViewerMock = {
|
|||
language: 'javascript',
|
||||
path: 'some_file.js',
|
||||
webPath: 'some_file.js',
|
||||
blamePath: 'blame/file.js',
|
||||
editBlobPath: 'some_file.js/edit',
|
||||
gitpodBlobUrl: 'https://gitpod.io#path/to/blob.js',
|
||||
ideEditPath: 'some_file.js/ide/edit',
|
||||
|
|
|
@ -130,7 +130,7 @@ describe('AlertDetails', () => {
|
|||
environmentData = { name: null, path: null };
|
||||
mountComponent();
|
||||
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBeFalsy();
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -10,16 +10,26 @@ const DEFAULT_PROPS = {
|
|||
number: 2,
|
||||
content: '// Line content',
|
||||
language: 'javascript',
|
||||
blamePath: 'blame/file.js',
|
||||
};
|
||||
|
||||
describe('Chunk Line component', () => {
|
||||
let wrapper;
|
||||
const fileLineBlame = true;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMountExtended(ChunkLine, { propsData: { ...DEFAULT_PROPS, ...props } });
|
||||
wrapper = shallowMountExtended(ChunkLine, {
|
||||
propsData: { ...DEFAULT_PROPS, ...props },
|
||||
provide: {
|
||||
glFeatures: {
|
||||
fileLineBlame,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findLink = () => wrapper.findByTestId('line-number-anchor');
|
||||
const findLineLink = () => wrapper.find('.file-line-num');
|
||||
const findBlameLink = () => wrapper.find('.file-line-blame');
|
||||
const findContent = () => wrapper.findByTestId('content');
|
||||
const findWrappedBidiChars = () => wrapper.findAllByTestId('bidi-wrapper');
|
||||
|
||||
|
@ -46,14 +56,22 @@ describe('Chunk Line component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('renders a blame link', () => {
|
||||
expect(findBlameLink().attributes()).toMatchObject({
|
||||
href: `${DEFAULT_PROPS.blamePath}#L${DEFAULT_PROPS.number}`,
|
||||
});
|
||||
|
||||
expect(findBlameLink().text()).toBe('');
|
||||
});
|
||||
|
||||
it('renders a line number', () => {
|
||||
expect(findLink().attributes()).toMatchObject({
|
||||
expect(findLineLink().attributes()).toMatchObject({
|
||||
'data-line-number': `${DEFAULT_PROPS.number}`,
|
||||
href: `#L${DEFAULT_PROPS.number}`,
|
||||
id: `L${DEFAULT_PROPS.number}`,
|
||||
});
|
||||
|
||||
expect(findLink().text()).toBe(DEFAULT_PROPS.number.toString());
|
||||
expect(findLineLink().text()).toBe(DEFAULT_PROPS.number.toString());
|
||||
});
|
||||
|
||||
it('renders content', () => {
|
||||
|
|
|
@ -10,6 +10,7 @@ const DEFAULT_PROPS = {
|
|||
startingFrom: 140,
|
||||
totalLines: 50,
|
||||
language: 'javascript',
|
||||
blamePath: 'blame/file.js',
|
||||
};
|
||||
|
||||
describe('Chunk component', () => {
|
||||
|
@ -76,6 +77,7 @@ describe('Chunk component', () => {
|
|||
number: DEFAULT_PROPS.startingFrom + 1,
|
||||
content: splitContent[0],
|
||||
language: DEFAULT_PROPS.language,
|
||||
blamePath: DEFAULT_PROPS.blamePath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,8 +40,9 @@ describe('Source Viewer component', () => {
|
|||
const chunk2 = generateContent('// Some source code 2', 70);
|
||||
const content = chunk1 + chunk2;
|
||||
const path = 'some/path.js';
|
||||
const blamePath = 'some/blame/path.js';
|
||||
const fileType = 'javascript';
|
||||
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, fileType };
|
||||
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, blamePath, fileType };
|
||||
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
|
||||
|
||||
const createComponent = async (blob = {}) => {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { stripTypenames } from 'helpers/graphql_helpers';
|
|||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
|
||||
import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
|
||||
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
|
||||
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
|
||||
|
@ -35,6 +36,7 @@ describe('WorkItemAssignees component', () => {
|
|||
const findAssigneeLinks = () => wrapper.findAllComponents(GlLink);
|
||||
const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
|
||||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findInviteMembersTrigger = () => wrapper.findComponent(InviteMembersTrigger);
|
||||
|
||||
const findEmptyState = () => wrapper.findByTestId('empty-state');
|
||||
const findAssignSelfButton = () => wrapper.findByTestId('assign-self');
|
||||
|
@ -57,6 +59,7 @@ describe('WorkItemAssignees component', () => {
|
|||
currentUserQueryHandler = successCurrentUserQueryHandler,
|
||||
updateWorkItemMutationHandler = successUpdateWorkItemMutationHandler,
|
||||
allowsMultipleAssignees = true,
|
||||
canInviteMembers = false,
|
||||
canUpdate = true,
|
||||
} = {}) => {
|
||||
const apolloProvider = createMockApollo(
|
||||
|
@ -89,6 +92,7 @@ describe('WorkItemAssignees component', () => {
|
|||
allowsMultipleAssignees,
|
||||
workItemType: TASK_TYPE_NAME,
|
||||
canUpdate,
|
||||
canInviteMembers,
|
||||
},
|
||||
attachTo: document.body,
|
||||
apolloProvider,
|
||||
|
@ -446,4 +450,18 @@ describe('WorkItemAssignees component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invite members', () => {
|
||||
it('does not render `Invite members` link if user has no permission to invite members', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findInviteMembersTrigger().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders `Invite members` link if user has a permission to invite members', () => {
|
||||
createComponent({ canInviteMembers: true });
|
||||
|
||||
expect(findInviteMembersTrigger().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,6 +48,7 @@ export const workItemQueryResponse = {
|
|||
__typename: 'WorkItemWidgetAssignees',
|
||||
type: 'ASSIGNEES',
|
||||
allowsMultipleAssignees: true,
|
||||
canInviteMembers: true,
|
||||
assignees: {
|
||||
nodes: mockAssignees,
|
||||
},
|
||||
|
@ -110,6 +111,7 @@ export const updateWorkItemMutationResponse = {
|
|||
__typename: 'WorkItemWidgetAssignees',
|
||||
type: 'ASSIGNEES',
|
||||
allowsMultipleAssignees: true,
|
||||
canInviteMembers: true,
|
||||
assignees: {
|
||||
nodes: [mockAssignees[0]],
|
||||
},
|
||||
|
@ -136,6 +138,7 @@ export const workItemResponseFactory = ({
|
|||
assigneesWidgetPresent = true,
|
||||
weightWidgetPresent = true,
|
||||
confidential = false,
|
||||
canInviteMembers = false,
|
||||
parent = mockParent.parent,
|
||||
} = {}) => ({
|
||||
data: {
|
||||
|
@ -169,6 +172,7 @@ export const workItemResponseFactory = ({
|
|||
__typename: 'WorkItemWidgetAssignees',
|
||||
type: 'ASSIGNEES',
|
||||
allowsMultipleAssignees,
|
||||
canInviteMembers,
|
||||
assignees: {
|
||||
nodes: mockAssignees,
|
||||
},
|
||||
|
|
|
@ -308,19 +308,19 @@ describe('Fly out sidebar navigation', () => {
|
|||
|
||||
describe('canShowSubItems', () => {
|
||||
it('returns true if on desktop size', () => {
|
||||
expect(canShowSubItems()).toBeTruthy();
|
||||
expect(canShowSubItems()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if on mobile size', () => {
|
||||
breakpointSize = 'xs';
|
||||
|
||||
expect(canShowSubItems()).toBeFalsy();
|
||||
expect(canShowSubItems()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canShowActiveSubItems', () => {
|
||||
it('returns true by default', () => {
|
||||
expect(canShowActiveSubItems(el)).toBeTruthy();
|
||||
expect(canShowActiveSubItems(el)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when active & expanded sidebar', () => {
|
||||
|
@ -329,7 +329,7 @@ describe('Fly out sidebar navigation', () => {
|
|||
|
||||
setSidebar(sidebar);
|
||||
|
||||
expect(canShowActiveSubItems(el)).toBeFalsy();
|
||||
expect(canShowActiveSubItems(el)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when active & collapsed sidebar', () => {
|
||||
|
@ -339,7 +339,7 @@ describe('Fly out sidebar navigation', () => {
|
|||
|
||||
setSidebar(sidebar);
|
||||
|
||||
expect(canShowActiveSubItems(el)).toBeTruthy();
|
||||
expect(canShowActiveSubItems(el)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
|
|||
}
|
||||
end
|
||||
|
||||
subject { described_class.new(ip) }
|
||||
subject(:rate_limiter) { described_class.new(ip) }
|
||||
|
||||
before do
|
||||
stub_rack_attack_setting(options)
|
||||
|
@ -25,7 +25,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
|
|||
end
|
||||
|
||||
after do
|
||||
subject.reset!
|
||||
rate_limiter.reset!
|
||||
end
|
||||
|
||||
describe '#register_fail!' do
|
||||
|
@ -86,7 +86,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
|
|||
end
|
||||
end
|
||||
|
||||
context 'when IP is whitlisted' do
|
||||
context 'when IP is allow listed' do
|
||||
let(:ip) { '127.0.0.1' }
|
||||
|
||||
it_behaves_like 'skips the rate limiter'
|
||||
|
@ -97,4 +97,20 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
|
|||
|
||||
it_behaves_like 'skips the rate limiter'
|
||||
end
|
||||
|
||||
describe '#trusted_ip?' do
|
||||
subject { rate_limiter.trusted_ip? }
|
||||
|
||||
context 'when ip is in the trusted list' do
|
||||
let(:ip) { '127.0.0.1' }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when ip is not in the trusted list' do
|
||||
let(:ip) { '10.0.0.1' }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -818,3 +818,6 @@ bulk_import_export:
|
|||
- group
|
||||
service_desk_setting:
|
||||
- file_template_project
|
||||
approvals:
|
||||
- user
|
||||
- merge_request
|
||||
|
|
|
@ -272,6 +272,11 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
expect(ProjectLabel.count).to eq(3)
|
||||
end
|
||||
|
||||
it 'has merge request approvals' do
|
||||
expect(MergeRequest.find_by(title: 'MR1').approvals.pluck(:user_id)).to contain_exactly(@user.id, *@existing_members.map(&:id))
|
||||
expect(MergeRequest.find_by(title: 'MR2').approvals).to be_empty
|
||||
end
|
||||
|
||||
it 'has no group labels' do
|
||||
expect(GroupLabel.count).to eq(0)
|
||||
end
|
||||
|
|
|
@ -100,6 +100,13 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
|
|||
expect(subject.first['notes'].first['author']).not_to be_empty
|
||||
end
|
||||
|
||||
it 'has merge request approvals' do
|
||||
approval = subject.first['approvals'].first
|
||||
|
||||
expect(approval).not_to be_nil
|
||||
expect(approval['user_id']).to eq(user.id)
|
||||
end
|
||||
|
||||
it 'has merge request resource label events' do
|
||||
expect(subject.first['resource_label_events']).not_to be_empty
|
||||
end
|
||||
|
@ -483,6 +490,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
|
|||
create(:label_priority, label: group_label, priority: 1)
|
||||
milestone = create(:milestone, project: project)
|
||||
merge_request = create(:merge_request, source_project: project, milestone: milestone, assignees: [user], reviewers: [user])
|
||||
create(:approval, merge_request: merge_request, user: user)
|
||||
|
||||
ci_build = create(:ci_build, project: project, when: nil)
|
||||
ci_build.pipeline.update!(project: project)
|
||||
|
|
|
@ -913,3 +913,7 @@ MergeRequest::CleanupSchedule:
|
|||
- completed_at
|
||||
- created_at
|
||||
- updated_at
|
||||
Approval:
|
||||
- user_id
|
||||
- created_at
|
||||
- updated_at
|
||||
|
|
|
@ -27,7 +27,7 @@ RSpec.describe Gitlab::Octokit::Middleware do
|
|||
it_behaves_like 'Public URL'
|
||||
end
|
||||
|
||||
context 'when the URL is a localhost adresss' do
|
||||
context 'when the URL is a localhost address' do
|
||||
let(:env) { { url: 'http://127.0.0.1' } }
|
||||
|
||||
context 'when localhost requests are not allowed' do
|
||||
|
|
|
@ -94,14 +94,6 @@ RSpec.describe LooseForeignKeys::DeletedRecord, type: :model do
|
|||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
|
||||
context 'when the lfk_automatic_partition_creation FF is off' do
|
||||
before do
|
||||
stub_feature_flags(lfk_automatic_partition_creation: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -126,14 +118,6 @@ RSpec.describe LooseForeignKeys::DeletedRecord, type: :model do
|
|||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
|
||||
context 'when the lfk_automatic_partition_dropping FF is off' do
|
||||
before do
|
||||
stub_feature_flags(lfk_automatic_partition_dropping: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -26,31 +26,20 @@ RSpec.describe ProjectStatistics do
|
|||
end
|
||||
|
||||
describe 'statistics columns' do
|
||||
it "support values up to 8 exabytes" do
|
||||
statistics.update!(
|
||||
commit_count: 8.exabytes - 1,
|
||||
repository_size: 2.exabytes,
|
||||
wiki_size: 1.exabytes,
|
||||
lfs_objects_size: 2.exabytes,
|
||||
build_artifacts_size: 1.exabyte,
|
||||
snippets_size: 1.exabyte,
|
||||
pipeline_artifacts_size: 512.petabytes - 1,
|
||||
uploads_size: 512.petabytes,
|
||||
container_registry_size: 12.petabytes
|
||||
)
|
||||
|
||||
statistics.reload
|
||||
|
||||
expect(statistics.commit_count).to eq(8.exabytes - 1)
|
||||
expect(statistics.repository_size).to eq(2.exabytes)
|
||||
expect(statistics.wiki_size).to eq(1.exabytes)
|
||||
expect(statistics.lfs_objects_size).to eq(2.exabytes)
|
||||
expect(statistics.build_artifacts_size).to eq(1.exabyte)
|
||||
expect(statistics.storage_size).to eq(8.exabytes - 1)
|
||||
expect(statistics.snippets_size).to eq(1.exabyte)
|
||||
expect(statistics.pipeline_artifacts_size).to eq(512.petabytes - 1)
|
||||
expect(statistics.uploads_size).to eq(512.petabytes)
|
||||
expect(statistics.container_registry_size).to eq(12.petabytes)
|
||||
it "supports bigint values" do
|
||||
expect do
|
||||
statistics.update!(
|
||||
commit_count: 3.gigabytes,
|
||||
repository_size: 3.gigabytes,
|
||||
wiki_size: 3.gigabytes,
|
||||
lfs_objects_size: 3.gigabytes,
|
||||
build_artifacts_size: 3.gigabytes,
|
||||
snippets_size: 3.gigabytes,
|
||||
pipeline_artifacts_size: 3.gigabytes,
|
||||
uploads_size: 3.gigabytes,
|
||||
container_registry_size: 3.gigabytes
|
||||
)
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -145,6 +145,18 @@ RSpec.describe 'getting group information' do
|
|||
expect(graphql_data_at(:group, :timelogCategories, :nodes))
|
||||
.to contain_exactly(a_graphql_entity_for(timelog_category))
|
||||
end
|
||||
|
||||
context 'when timelog_categories flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(timelog_categories: false)
|
||||
end
|
||||
|
||||
it 'returns no timelog categories' do
|
||||
post_graphql(group_query(group), current_user: user2)
|
||||
|
||||
expect(graphql_data_at(:group, :timelogCategories)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for N+1 queries' do
|
||||
|
|
|
@ -229,6 +229,18 @@ RSpec.describe 'getting project information' do
|
|||
expect(graphql_data_at(:project, :timelogCategories, :nodes))
|
||||
.to contain_exactly(a_graphql_entity_for(timelog_category))
|
||||
end
|
||||
|
||||
context 'when timelog_categories flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(timelog_categories: false)
|
||||
end
|
||||
|
||||
it 'returns no timelog categories' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
expect(graphql_data_at(:project, :timelogCategories)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for N+1 queries' do
|
||||
|
|
|
@ -376,10 +376,17 @@ RSpec.describe API::Internal::Base do
|
|||
shared_examples 'rate limited request' do
|
||||
let(:action) { 'git-upload-pack' }
|
||||
let(:actor) { key }
|
||||
let(:rate_limiter) { double(:rate_limiter, ip: "127.0.0.1", trusted_ip?: false) }
|
||||
|
||||
before do
|
||||
allow(::Gitlab::Auth::IpRateLimiter).to receive(:new).with("127.0.0.1").and_return(rate_limiter)
|
||||
end
|
||||
|
||||
it 'is throttled by rate limiter' do
|
||||
allow(::Gitlab::ApplicationRateLimiter).to receive(:threshold).and_return(1)
|
||||
|
||||
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:gitlab_shell_operation, scope: [action, project.full_path, actor]).twice.and_call_original
|
||||
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:gitlab_shell_operation, scope: [action, project.full_path, "127.0.0.1"]).and_call_original
|
||||
|
||||
request
|
||||
|
||||
|
@ -402,6 +409,28 @@ RSpec.describe API::Internal::Base do
|
|||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rate_limit_gitlab_shell_by_ip feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(rate_limit_gitlab_shell_by_ip: false)
|
||||
end
|
||||
|
||||
it 'is not throttled by rate limiter' do
|
||||
expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the IP is in a trusted range' do
|
||||
let(:rate_limiter) { double(:rate_limiter, ip: "127.0.0.1", trusted_ip?: true) }
|
||||
|
||||
it 'is not throttled by rate limiter' do
|
||||
expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "access granted" do
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require_relative '../../../../rubocop/cop/gemspec/avoid_executing_git'
|
||||
|
||||
RSpec.describe RuboCop::Cop::Gemspec::AvoidExecutingGit do
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
it 'flags violation for executing git' do
|
||||
expect_offense(<<~RUBY)
|
||||
Gem::Specification.new do |gem|
|
||||
gem.executable = `git ls-files -- bin/*`.split("\\n").map{ |f| File.basename(f) }
|
||||
^^^^^^^^^^^^^^^^^^^^^^^ Do not execute `git` in gemspec.
|
||||
gem.files = `git ls-files`.split("\\n")
|
||||
^^^^^^^^^^^^^^ Do not execute `git` in gemspec.
|
||||
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\\n")
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not execute `git` in gemspec.
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'does not flag violation for using a glob' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
Gem::Specification.new do |gem|
|
||||
gem.files = Dir.glob("lib/**/*.*")
|
||||
gem.test_files = Dir.glob("spec/**/**/*.*")
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
|
@ -69,6 +69,12 @@ RSpec.describe Issues::CreateService do
|
|||
expect(issue.issue_customer_relations_contacts).to be_empty
|
||||
end
|
||||
|
||||
it 'calls NewIssueWorker with correct arguments' do
|
||||
expect(NewIssueWorker).to receive(:perform_async).with(Integer, user.id, 'Issue')
|
||||
|
||||
issue
|
||||
end
|
||||
|
||||
context 'when a build_service is provided' do
|
||||
let(:issue) { described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params, build_service: build_service).execute }
|
||||
|
||||
|
@ -143,6 +149,12 @@ RSpec.describe Issues::CreateService do
|
|||
issue
|
||||
end
|
||||
|
||||
it 'calls NewIssueWorker with correct arguments' do
|
||||
expect(NewIssueWorker).to receive(:perform_async).with(Integer, reporter.id, 'Issue')
|
||||
|
||||
issue
|
||||
end
|
||||
|
||||
context 'when invalid' do
|
||||
before do
|
||||
opts.merge!(title: '')
|
||||
|
|
|
@ -65,6 +65,12 @@ RSpec.describe WorkItems::CreateService do
|
|||
expect(work_item.description).to eq('please fix')
|
||||
expect(work_item.work_item_type.base_type).to eq('issue')
|
||||
end
|
||||
|
||||
it 'calls NewIssueWorker with correct arguments' do
|
||||
expect(NewIssueWorker).to receive(:perform_async).with(Integer, current_user.id, 'WorkItem')
|
||||
|
||||
service_result
|
||||
end
|
||||
end
|
||||
|
||||
context 'when params are invalid' do
|
||||
|
|
|
@ -24,6 +24,7 @@ RSpec.describe 'projects/blob/_viewer.html.haml' do
|
|||
before do
|
||||
assign(:project, project)
|
||||
assign(:blob, blob)
|
||||
assign(:ref, 'master')
|
||||
assign(:id, File.join('master', blob.path))
|
||||
|
||||
controller.params[:controller] = 'projects/blob'
|
||||
|
|
|
@ -74,6 +74,8 @@ RSpec.describe NewIssueWorker do
|
|||
|
||||
it 'creates a new event record' do
|
||||
expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
|
||||
|
||||
expect(Event.last).to have_attributes(target_id: issue.id, target_type: 'Issue')
|
||||
end
|
||||
|
||||
it 'creates a notification for the mentioned user' do
|
||||
|
@ -89,6 +91,14 @@ RSpec.describe NewIssueWorker do
|
|||
|
||||
worker.perform(issue.id, user.id)
|
||||
end
|
||||
|
||||
context 'when a class is set' do
|
||||
it 'creates event with the correct type' do
|
||||
expect { worker.perform(issue.id, user.id, 'WorkItem') }.to change { Event.count }.from(0).to(1)
|
||||
|
||||
expect(Event.last).to have_attributes(target_id: issue.id, target_type: 'WorkItem')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ require (
|
|||
github.com/johannesboyne/gofakes3 v0.0.0-20220627085814-c3ac35da23b2
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/rafaeljusto/redigomock/v3 v3.1.1
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
|
@ -89,8 +89,8 @@ require (
|
|||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
||||
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.21.2 // indirect
|
||||
|
|
|
@ -425,9 +425,11 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
|
@ -949,8 +951,9 @@ github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP
|
|||
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
|
||||
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
|
@ -964,16 +967,18 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2
|
|||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/rafaeljusto/redigomock/v3 v3.1.1 h1:SdWE9v+SPy3x6G5hS3aofIJgHJY3OdBJ0BdUTk4dYbA=
|
||||
github.com/rafaeljusto/redigomock/v3 v3.1.1/go.mod h1:F9zPqz8rMriScZkPtUiLJoLruYcpGo/XXREpeyasREM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
|
|
26
yarn.lock
26
yarn.lock
|
@ -8100,6 +8100,13 @@ mdast-util-from-markdown@^1.0.0:
|
|||
unist-util-stringify-position "^3.0.0"
|
||||
uvu "^0.5.0"
|
||||
|
||||
mdast-util-frontmatter@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-1.0.0.tgz#ef12469379782e4a0fd995fed60cc3b871e6c819"
|
||||
integrity sha512-7itKvp0arEVNpCktOET/eLFAYaZ+0cNjVtFtIPxgQ5tV+3i+D4SDDTjTzPWl44LT59PC+xdx+glNTawBdF98Mw==
|
||||
dependencies:
|
||||
micromark-extension-frontmatter "^1.0.0"
|
||||
|
||||
mdast-util-gfm-autolink-literal@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz#4032dcbaddaef7d4f2f3768ed830475bb22d3970"
|
||||
|
@ -8321,6 +8328,15 @@ micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1:
|
|||
micromark-util-types "^1.0.1"
|
||||
uvu "^0.5.0"
|
||||
|
||||
micromark-extension-frontmatter@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-1.0.0.tgz#612498e6dad87c132c95e25f0918e7cc0cd535f6"
|
||||
integrity sha512-EXjmRnupoX6yYuUJSQhrQ9ggK0iQtQlpi6xeJzVD5xscyAI+giqco5fdymayZhJMbIFecjnE2yz85S9NzIgQpg==
|
||||
dependencies:
|
||||
fault "^2.0.0"
|
||||
micromark-util-character "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-extension-gfm-autolink-literal@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz#dc589f9c37eaff31a175bab49f12290edcf96058"
|
||||
|
@ -10176,6 +10192,16 @@ rehype-raw@^6.1.1:
|
|||
hast-util-raw "^7.2.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
remark-frontmatter@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-4.0.1.tgz#84560f7ccef114ef076d3d3735be6d69f8922309"
|
||||
integrity sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
mdast-util-frontmatter "^1.0.0"
|
||||
micromark-extension-frontmatter "^1.0.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
remark-gfm@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f"
|
||||
|
|
Loading…
Reference in New Issue