Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a8648ba086
commit
981771279a
|
@ -1 +1 @@
|
|||
8fe0f38a98ddcc5e2cb68f7593e1514c6a1287b1
|
||||
0a4e1c785063f4ad2cef80303fa10276e2e4e2a6
|
||||
|
|
|
@ -54,6 +54,7 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
inject: ['reportAbusePath'],
|
||||
props: {
|
||||
snippet: {
|
||||
type: Object,
|
||||
|
@ -93,7 +94,6 @@ export default {
|
|||
click: this.showDeleteModal,
|
||||
variant: 'danger',
|
||||
category: 'secondary',
|
||||
cssClass: 'ml-2',
|
||||
},
|
||||
{
|
||||
condition: this.canCreateSnippet,
|
||||
|
@ -103,10 +103,18 @@ export default {
|
|||
: joinPaths('/', gon.relative_url_root, '/-/snippets/new'),
|
||||
variant: 'success',
|
||||
category: 'secondary',
|
||||
cssClass: 'ml-2',
|
||||
},
|
||||
{
|
||||
condition: this.reportAbusePath,
|
||||
text: __('Submit as spam'),
|
||||
href: this.reportAbusePath,
|
||||
title: __('Submit as spam'),
|
||||
},
|
||||
];
|
||||
},
|
||||
hasPersonalSnippetActions() {
|
||||
return Boolean(this.personalSnippetActions.filter(({ condition }) => condition).length);
|
||||
},
|
||||
editLink() {
|
||||
return `${this.snippet.webUrl}/edit`;
|
||||
},
|
||||
|
@ -212,7 +220,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-page-header-actions">
|
||||
<div v-if="hasPersonalSnippetActions" class="detail-page-header-actions">
|
||||
<div class="d-none d-sm-flex">
|
||||
<template v-for="(action, index) in personalSnippetActions">
|
||||
<div
|
||||
|
@ -221,6 +229,7 @@ export default {
|
|||
v-gl-tooltip
|
||||
:title="action.title"
|
||||
class="d-inline-block"
|
||||
:class="{ 'gl-ml-3': index > 0 }"
|
||||
>
|
||||
<gl-button
|
||||
:disabled="action.disabled"
|
||||
|
@ -239,15 +248,17 @@ export default {
|
|||
</div>
|
||||
<div class="d-block d-sm-none dropdown">
|
||||
<gl-dropdown :text="__('Options')" block>
|
||||
<gl-dropdown-item
|
||||
v-for="(action, index) in personalSnippetActions"
|
||||
:key="index"
|
||||
:disabled="action.disabled"
|
||||
:title="action.title"
|
||||
:href="action.href"
|
||||
@click="action.click ? action.click() : undefined"
|
||||
>{{ action.text }}</gl-dropdown-item
|
||||
>
|
||||
<template v-for="(action, index) in personalSnippetActions">
|
||||
<gl-dropdown-item
|
||||
v-if="action.condition"
|
||||
:key="index"
|
||||
:disabled="action.disabled"
|
||||
:title="action.title"
|
||||
:href="action.href"
|
||||
@click="action.click ? action.click() : undefined"
|
||||
>{{ action.text }}</gl-dropdown-item
|
||||
>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,6 +27,7 @@ export default function appFactory(el, Component) {
|
|||
visibilityLevels = '[]',
|
||||
selectedLevel,
|
||||
multipleLevelsRestricted,
|
||||
reportAbusePath,
|
||||
...restDataset
|
||||
} = el.dataset;
|
||||
|
||||
|
@ -37,6 +38,7 @@ export default function appFactory(el, Component) {
|
|||
visibilityLevels: JSON.parse(visibilityLevels),
|
||||
selectedLevel: SNIPPET_LEVELS_MAP[selectedLevel] ?? SNIPPET_VISIBILITY_PRIVATE,
|
||||
multipleLevelsRestricted: 'multipleLevelsRestricted' in el.dataset,
|
||||
reportAbusePath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(Component, {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlPopover, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlPopover, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import $ from 'jquery';
|
||||
import { keysFor, BOLD_TEXT, ITALIC_TEXT, LINK_TEXT } from '~/behaviors/shortcuts/keybindings';
|
||||
import { getSelectedFragment } from '~/lib/utils/common_utils';
|
||||
|
@ -10,6 +10,7 @@ import ToolbarButton from './toolbar_button.vue';
|
|||
export default {
|
||||
components: {
|
||||
ToolbarButton,
|
||||
GlIcon,
|
||||
GlPopover,
|
||||
GlButton,
|
||||
},
|
||||
|
@ -45,7 +46,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
tag: '> ',
|
||||
suggestPopoverVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -76,27 +76,15 @@ export default {
|
|||
return this.isMac ? '⌘' : s__('KeyboardKey|Ctrl+');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
showSuggestPopover() {
|
||||
this.updateSuggestPopoverVisibility();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
|
||||
$(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
|
||||
|
||||
this.updateSuggestPopoverVisibility();
|
||||
},
|
||||
beforeDestroy() {
|
||||
$(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
|
||||
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
|
||||
},
|
||||
methods: {
|
||||
async updateSuggestPopoverVisibility() {
|
||||
await this.$nextTick();
|
||||
|
||||
this.suggestPopoverVisible = this.showSuggestPopover && this.canSuggest;
|
||||
},
|
||||
isValid(form) {
|
||||
return (
|
||||
!form ||
|
||||
|
@ -165,114 +153,127 @@ export default {
|
|||
</button>
|
||||
</li>
|
||||
<li :class="{ active: !previewMarkdown }" class="md-header-toolbar">
|
||||
<toolbar-button
|
||||
tag="**"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
|
||||
"
|
||||
:shortcuts="$options.shortcuts.bold"
|
||||
icon="bold"
|
||||
/>
|
||||
<toolbar-button
|
||||
tag="_"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
|
||||
"
|
||||
:shortcuts="$options.shortcuts.italic"
|
||||
icon="italic"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
:tag="tag"
|
||||
:button-title="__('Insert a quote')"
|
||||
icon="quote"
|
||||
@click="handleQuote"
|
||||
/>
|
||||
<template v-if="canSuggest">
|
||||
<div class="d-inline-block">
|
||||
<toolbar-button
|
||||
ref="suggestButton"
|
||||
:tag="mdSuggestion"
|
||||
:prepend="true"
|
||||
:button-title="__('Insert suggestion')"
|
||||
:cursor-offset="4"
|
||||
:tag-content="lineContent"
|
||||
icon="doc-code"
|
||||
data-qa-selector="suggestion_button"
|
||||
class="js-suggestion-btn"
|
||||
@click="handleSuggestDismissed"
|
||||
tag="**"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
|
||||
"
|
||||
:shortcuts="$options.shortcuts.bold"
|
||||
icon="bold"
|
||||
/>
|
||||
<gl-popover
|
||||
v-if="suggestPopoverVisible"
|
||||
:target="$refs.suggestButton.$el"
|
||||
:css-classes="['diff-suggest-popover']"
|
||||
placement="bottom"
|
||||
:show="suggestPopoverVisible"
|
||||
>
|
||||
<strong>{{ __('New! Suggest changes directly') }}</strong>
|
||||
<p class="mb-2">
|
||||
{{
|
||||
__(
|
||||
'Suggest code changes which can be immediately applied in one click. Try it out!',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<gl-button
|
||||
variant="info"
|
||||
category="primary"
|
||||
size="small"
|
||||
<toolbar-button
|
||||
tag="_"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
|
||||
"
|
||||
:shortcuts="$options.shortcuts.italic"
|
||||
icon="italic"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
:tag="tag"
|
||||
:button-title="__('Insert a quote')"
|
||||
icon="quote"
|
||||
@click="handleQuote"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-inline-block ml-md-2 ml-0">
|
||||
<template v-if="canSuggest">
|
||||
<toolbar-button
|
||||
ref="suggestButton"
|
||||
:tag="mdSuggestion"
|
||||
:prepend="true"
|
||||
:button-title="__('Insert suggestion')"
|
||||
:cursor-offset="4"
|
||||
:tag-content="lineContent"
|
||||
icon="doc-code"
|
||||
data-qa-selector="suggestion_button"
|
||||
class="js-suggestion-btn"
|
||||
@click="handleSuggestDismissed"
|
||||
/>
|
||||
<gl-popover
|
||||
v-if="showSuggestPopover && $refs.suggestButton"
|
||||
:target="$refs.suggestButton"
|
||||
:css-classes="['diff-suggest-popover']"
|
||||
placement="bottom"
|
||||
:show="showSuggestPopover"
|
||||
>
|
||||
{{ __('Got it') }}
|
||||
</gl-button>
|
||||
</gl-popover>
|
||||
</template>
|
||||
<toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
|
||||
<toolbar-button
|
||||
tag="[{text}](url)"
|
||||
tag-select="url"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
|
||||
"
|
||||
:shortcuts="$options.shortcuts.link"
|
||||
icon="link"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
tag="- "
|
||||
:button-title="__('Add a bullet list')"
|
||||
icon="list-bulleted"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
tag="1. "
|
||||
:button-title="__('Add a numbered list')"
|
||||
icon="list-numbered"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
tag="- [ ] "
|
||||
:button-title="__('Add a task list')"
|
||||
icon="list-task"
|
||||
/>
|
||||
<toolbar-button
|
||||
:tag="mdCollapsibleSection"
|
||||
:prepend="true"
|
||||
tag-select="Click to expand"
|
||||
:button-title="__('Add a collapsible section')"
|
||||
icon="details-block"
|
||||
/>
|
||||
<toolbar-button
|
||||
:tag="mdTable"
|
||||
:prepend="true"
|
||||
:button-title="__('Add a table')"
|
||||
icon="table"
|
||||
/>
|
||||
<toolbar-button
|
||||
class="js-zen-enter"
|
||||
:prepend="true"
|
||||
:button-title="__('Go full screen')"
|
||||
icon="maximize"
|
||||
/>
|
||||
<strong>{{ __('New! Suggest changes directly') }}</strong>
|
||||
<p class="mb-2">
|
||||
{{
|
||||
__(
|
||||
'Suggest code changes which can be immediately applied in one click. Try it out!',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<gl-button
|
||||
variant="info"
|
||||
category="primary"
|
||||
size="sm"
|
||||
@click="handleSuggestDismissed"
|
||||
>
|
||||
{{ __('Got it') }}
|
||||
</gl-button>
|
||||
</gl-popover>
|
||||
</template>
|
||||
<toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
|
||||
<toolbar-button
|
||||
tag="[{text}](url)"
|
||||
tag-select="url"
|
||||
:button-title="
|
||||
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
|
||||
"
|
||||
:shortcuts="$options.shortcuts.link"
|
||||
icon="link"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-inline-block ml-md-2 ml-0">
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
tag="- "
|
||||
:button-title="__('Add a bullet list')"
|
||||
icon="list-bulleted"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
tag="1. "
|
||||
:button-title="__('Add a numbered list')"
|
||||
icon="list-numbered"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
tag="- [ ] "
|
||||
:button-title="__('Add a task list')"
|
||||
icon="list-task"
|
||||
/>
|
||||
<toolbar-button
|
||||
:tag="mdCollapsibleSection"
|
||||
:prepend="true"
|
||||
tag-select="Click to expand"
|
||||
:button-title="__('Add a collapsible section')"
|
||||
icon="details-block"
|
||||
/>
|
||||
<toolbar-button
|
||||
:tag="mdTable"
|
||||
:prepend="true"
|
||||
:button-title="__('Add a table')"
|
||||
icon="table"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-inline-block ml-md-2 ml-0">
|
||||
<button
|
||||
v-gl-tooltip
|
||||
:aria-label="__('Go full screen')"
|
||||
class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
|
||||
data-container="body"
|
||||
tabindex="-1"
|
||||
:title="__('Go full screen')"
|
||||
type="button"
|
||||
>
|
||||
<gl-icon name="maximize" />
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
|
||||
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlIcon,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -19,8 +19,7 @@ export default {
|
|||
},
|
||||
tag: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
tagBlock: {
|
||||
type: String,
|
||||
|
@ -72,7 +71,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-button
|
||||
<button
|
||||
v-gl-tooltip
|
||||
:data-md-tag="tag"
|
||||
:data-md-cursor-offset="cursorOffset"
|
||||
|
@ -83,11 +82,11 @@ export default {
|
|||
:data-md-shortcuts="shortcutsString"
|
||||
:title="buttonTitle"
|
||||
:aria-label="buttonTitle"
|
||||
:icon="icon"
|
||||
type="button"
|
||||
category="tertiary"
|
||||
class="toolbar-btn js-md"
|
||||
data-container="body"
|
||||
@click="() => $emit('click')"
|
||||
/>
|
||||
>
|
||||
<gl-icon :name="icon" />
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
@ -5,5 +5,6 @@ import createDefaultClient from '~/lib/graphql';
|
|||
Vue.use(VueApollo);
|
||||
|
||||
export default new VueApollo({
|
||||
assumeImmutableResults: true,
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
|
|
@ -83,7 +83,6 @@
|
|||
li.md-header-toolbar {
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
padding-bottom: $gl-padding-8;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
|
@ -92,9 +91,9 @@
|
|||
flex: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: $gl-padding-8;
|
||||
width: 100%;
|
||||
padding-top: $gl-padding-top;
|
||||
padding-bottom: $gl-padding-top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +131,36 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
float: left;
|
||||
padding: 0 7px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
fill: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
svg {
|
||||
fill: $blue-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-fullscreen-btn {
|
||||
margin-right: -5px;
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.md-suggestion-diff {
|
||||
display: table !important;
|
||||
border: 1px solid $border-color !important;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
a,
|
||||
button {
|
||||
padding: $gl-padding-8;
|
||||
padding-bottom: $gl-padding-8 + 1;
|
||||
font-size: 14px;
|
||||
line-height: 28px;
|
||||
color: $gl-text-color-secondary;
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
.common-note-form {
|
||||
.md-area {
|
||||
padding: $gl-padding-8 $gl-padding;
|
||||
padding: $gl-padding-top $gl-padding;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $border-radius-base;
|
||||
transition: border-color ease-in-out 0.15s,
|
||||
|
@ -323,7 +323,7 @@ table {
|
|||
}
|
||||
|
||||
.comment-toolbar {
|
||||
padding-top: $gl-padding-8;
|
||||
padding-top: $gl-padding-top;
|
||||
color: $gl-text-color-secondary;
|
||||
border-top: 1px solid $border-color;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,12 @@ module SnippetsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def snippet_report_abuse_path(snippet)
|
||||
return unless snippet.submittable_as_spam_by?(current_user)
|
||||
|
||||
mark_as_spam_snippet_path(snippet)
|
||||
end
|
||||
|
||||
def embedded_raw_snippet_button(snippet, blob)
|
||||
return if blob.empty? || blob.binary? || blob.stored_externally?
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- breadcrumb_title @snippet.to_reference
|
||||
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
|
||||
|
||||
#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id} }
|
||||
#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id, 'report-abuse-path': snippet_report_abuse_path(@snippet) } }
|
||||
|
||||
.row-content-block.top-block.content-component-block
|
||||
= render 'award_emoji/awards_block', awardable: @snippet, inline: true, api_awards_path: project_snippets_award_api_path(@snippet)
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
- content_for :prefetch_asset_tags do
|
||||
- webpack_preload_asset_tag('monaco', prefetch: true)
|
||||
|
||||
#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id} }
|
||||
#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id, 'report-abuse-path': snippet_report_abuse_path(@snippet) } }
|
||||
|
||||
.row-content-block.top-block.content-component-block
|
||||
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
|
||||
|
|
|
@ -4,11 +4,11 @@ group: Runner
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Linux shared runners
|
||||
# Build Cloud runners for Linux
|
||||
|
||||
Linux shared runners on GitLab.com run in autoscale mode and are powered by Google Cloud Platform.
|
||||
GitLab Build Cloud runners for Linux run in autoscale mode and are powered by Google Cloud Platform.
|
||||
|
||||
Autoscaling means reduced queue times to spin up CI/CD jobs, and isolated VMs for each job, thus maximizing security. These shared runners are available for users and customers on GitLab.com.
|
||||
Autoscaling means reduced queue times to spin up CI/CD jobs, and isolated VMs for each job, thus maximizing security. These shared runners are available on GitLab.com.
|
||||
|
||||
GitLab offers Ultimate tier capabilities and included CI/CD minutes per group per month for our [Open Source](https://about.gitlab.com/solutions/open-source/join/), [Education](https://about.gitlab.com/solutions/education/), and [Startups](https://about.gitlab.com/solutions/startups/) programs. For private projects, GitLab offers various [plans](https://about.gitlab.com/pricing/), starting with a Free tier.
|
||||
|
||||
|
@ -23,7 +23,7 @@ Jobs handled by the shared runners on GitLab.com (`shared-runners-manager-X.gitl
|
|||
**time out after 3 hours**, regardless of the timeout configured in a
|
||||
project. Check the issues [4010](https://gitlab.com/gitlab-com/infrastructure/-/issues/4010) and [4070](https://gitlab.com/gitlab-com/infrastructure/-/issues/4070) for the reference.
|
||||
|
||||
Below are the shared runners settings.
|
||||
Below are the runners' settings.
|
||||
|
||||
| Setting | GitLab.com | Default |
|
||||
| ----------- | ----------------- | ---------- |
|
||||
|
@ -33,7 +33,7 @@ Below are the shared runners settings.
|
|||
|
||||
## Pre-clone script
|
||||
|
||||
Linux shared runners on GitLab.com provide a way to run commands in a CI
|
||||
Build Cloud runners for Linux provide a way to run commands in a CI
|
||||
job before the runner attempts to run `git init` and `git fetch` to
|
||||
download a GitLab repository. The
|
||||
[`pre_clone_script`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Runner
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# VM instances and images for Build Cloud for macOS
|
||||
|
||||
When you use the Build Cloud for macOS:
|
||||
|
||||
- Each of your jobs runs in a newly provisioned VM, which is dedicated to the specific job.
|
||||
- The VM is active only for the duration of the job and immediately deleted.
|
||||
|
||||
## VM types
|
||||
|
||||
The virtual machine where your job runs has `sudo` access with no password.
|
||||
For the beta, there is only one available machine type, `gbc-macos-large`.
|
||||
|
||||
| Instance type | vCPUS | Memory (GB) |
|
||||
| --------- | --- | ------- |
|
||||
| `gbc-macos-large` | 4 | 10 |
|
||||
|
||||
## VM images
|
||||
|
||||
You can execute your build on one of the following images.
|
||||
You specify this image in your `.gitlab-ci.yml` file.
|
||||
|
||||
Each image is running a specific version of macOS and Xcode.
|
||||
|
||||
| VM image | Included software |
|
||||
|---------------------------|--------------------|
|
||||
| macos-10.13-xcode-7 | <https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/macstadium/orka/-/blob/main/toolchain/high-sierra.yml> |
|
||||
| macos-10.13-xcode-8 | <https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/macstadium/orka/-/blob/main/toolchain/high-sierra.yml> |
|
||||
| macos-10.13-xcode-9 | <https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/macstadium/orka/-/blob/main/toolchain/high-sierra.yml> |
|
||||
| macos-10.14-xcode-10 | <https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/macstadium/orka/-/blob/main/toolchain/mojave.yml> |
|
||||
| macos-10.15-xcode-11 | <https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/macstadium/orka/-/blob/main/toolchain/catalina.yml> |
|
||||
| macos-11-xcode-12 | <https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/macstadium/orka/-/blob/main/toolchain/big-sur.yml> |
|
||||
|
||||
### Image update policy
|
||||
|
||||
- Support for new macOS versions is planned.
|
||||
- Additional details on the support policy and image update release process are documented
|
||||
[in this project](https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/macstadium/orka/-/blob/55bf59c8fa88712960afff2bf6ecc5daa879a8f5/docs/overview.md#os-images).
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Runner
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Build Cloud runners for macOS (beta)
|
||||
|
||||
Build Cloud for macOS Beta provides on-demand GitLab Runners integrated with GitLab SaaS [CI/CD](../../../ci/index.md)
|
||||
to build, test, and deploy apps for the Apple ecosystem (macOS, iOS, tvOS). You can take advantage
|
||||
of all the capabilities of the GitLab single DevOps platform and not have to manage or operate a
|
||||
build environment.
|
||||
|
||||
Build Cloud runners for macOS are in [beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta)
|
||||
and shouldn't be relied upon for mission-critical production jobs.
|
||||
|
||||
## Quickstart
|
||||
|
||||
To start using Build Cloud for macOS beta, you must submit an access request issue. After your
|
||||
access has been granted and your build environment configured, you must configure your
|
||||
`.gitlab-ci.yml` pipeline file:
|
||||
|
||||
1. Add a `.gitlab-ci.yml` file to your project repository.
|
||||
1. Commit a change to your repository.
|
||||
|
||||
The runners automatically run your build.
|
||||
|
||||
## Example `.gitlab-ci.yml` file
|
||||
|
||||
The following sample `.gitlab-ci.yml` file shows how to start using the runners for macOS:
|
||||
|
||||
```yaml
|
||||
.macos_buildcloud_runners:
|
||||
tags:
|
||||
- shared-macos-amd64
|
||||
image: macos-11-xcode-12
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
|
||||
before_script:
|
||||
- echo "started by ${GITLAB_USER_NAME}"
|
||||
|
||||
build:
|
||||
extends:
|
||||
- .macos_buildcloud_runners
|
||||
stage: build
|
||||
script:
|
||||
- echo "running scripts in the build job"
|
||||
|
||||
test:
|
||||
extends:
|
||||
- .macos_buildcloud_runners
|
||||
stage: test
|
||||
script:
|
||||
- echo "running scripts in the test job"
|
||||
```
|
||||
|
||||
NOTE:
|
||||
During the beta period, the architecture of this solution will change. Rather than the jobs running on a specific VM instance, they will run on an ephemeral VM instance that is created by an autoscaling instance, known as the Runner Manager. We will notify all beta participants of any downtime required to do this work.
|
|
@ -4,24 +4,24 @@ group: Runner
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Windows shared runners (beta)
|
||||
# Build Cloud runners for Windows (beta)
|
||||
|
||||
The Windows shared runners are in [beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta)
|
||||
GitLab Build Cloud runners for Windows are in [beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta)
|
||||
and shouldn't be used for production workloads.
|
||||
|
||||
During this beta period, the [shared runner pipeline quota](../../../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota)
|
||||
applies for groups and projects in the same manner as Linux runners. This may
|
||||
change when the beta period ends, as discussed in this [related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/30834).
|
||||
|
||||
Windows shared runners on GitLab.com autoscale by launching virtual machines on
|
||||
Windows runners on GitLab.com autoscale by launching virtual machines on
|
||||
the Google Cloud Platform. This solution uses an
|
||||
[autoscaling driver](https://gitlab.com/gitlab-org/ci-cd/custom-executor-drivers/autoscaler/tree/master/docs/readme.md)
|
||||
developed by GitLab for the [custom executor](https://docs.gitlab.com/runner/executors/custom.html).
|
||||
Windows shared runners execute your CI/CD jobs on `n1-standard-2` instances with
|
||||
Windows runners execute your CI/CD jobs on `n1-standard-2` instances with
|
||||
2 vCPUs and 7.5 GB RAM. You can find a full list of available Windows packages in
|
||||
the [package documentation](https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/gcp/windows-containers/blob/master/cookbooks/preinstalled-software/README.md).
|
||||
|
||||
We want to keep iterating to get Windows shared runners in a stable state and
|
||||
We want to keep iterating to get Windows runners in a stable state and
|
||||
[generally available](https://about.gitlab.com/handbook/product/gitlab-the-product/#generally-available-ga).
|
||||
You can follow our work towards this goal in the
|
||||
[related epic](https://gitlab.com/groups/gitlab-org/-/epics/2162).
|
||||
|
@ -89,10 +89,9 @@ VMTag = "windows"
|
|||
Directory = "C:\\GitLab-Runner\\autoscaler\\machines"
|
||||
```
|
||||
|
||||
## Example
|
||||
## Example `.gitlab-ci.yml` file
|
||||
|
||||
Below is a simple `.gitlab-ci.yml` file to show how to start using the
|
||||
Windows shared runners:
|
||||
Below is a sample `.gitlab-ci.yml` file that shows how to start using the runners for Windows:
|
||||
|
||||
```yaml
|
||||
.shared_windows_runners:
|
||||
|
@ -131,14 +130,14 @@ test:
|
|||
definition](https://about.gitlab.com/handbook/product/#beta).
|
||||
- The average provisioning time for a new Windows VM is 5 minutes.
|
||||
This means that you may notice slower build start times
|
||||
on the Windows shared runner fleet during the beta. In a future
|
||||
on the Windows runner fleet during the beta. In a future
|
||||
release we intend to update the autoscaler to enable
|
||||
the pre-provisioning of virtual machines. This is intended to significantly reduce
|
||||
the time it takes to provision a VM on the Windows fleet. You can
|
||||
follow along in the [related issue](https://gitlab.com/gitlab-org/ci-cd/custom-executor-drivers/autoscaler/-/issues/32).
|
||||
- The Windows shared runner fleet may be unavailable occasionally
|
||||
- The Windows runner fleet may be unavailable occasionally
|
||||
for maintenance or updates.
|
||||
- The Windows shared runner virtual machine instances do not use the
|
||||
- The Windows runner virtual machine instances do not use the
|
||||
GitLab Docker executor. This means that you can't specify
|
||||
[`image`](../../../ci/yaml/index.md#image) or [`services`](../../../ci/yaml/index.md#services) in
|
||||
your pipeline configuration.
|
||||
|
@ -150,7 +149,7 @@ test:
|
|||
installation of additional software packages needs to be repeated for
|
||||
each job in your pipeline.
|
||||
- The job may stay in a pending state for longer than the
|
||||
Linux shared runners.
|
||||
Linux runners.
|
||||
- There is the possibility that we introduce breaking changes which will
|
||||
require updates to pipelines that are using the Windows shared runner
|
||||
require updates to pipelines that are using the Windows runner
|
||||
fleet.
|
||||
|
|
|
@ -5,14 +5,18 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: reference
|
||||
---
|
||||
|
||||
# GitLab SaaS runners
|
||||
# GitLab Build Cloud runners
|
||||
|
||||
If you are using self-managed GitLab or you want to use your own runners on GitLab.com, you can
|
||||
[install and configure your own runners](https://docs.gitlab.com/runner/install/).
|
||||
|
||||
If you are using GitLab SaaS (GitLab.com), your CI jobs automatically run on shared runners. No configuration is required.
|
||||
Your jobs can run on [Linux](build_cloud/linux_build_cloud.md) or [Windows](build_cloud/windows_build_cloud.md).
|
||||
If you are using GitLab SaaS (GitLab.com), your CI jobs automatically run on runners in the GitLab Build Cloud.
|
||||
No configuration is required. Your jobs can run on:
|
||||
|
||||
The number of minutes you can use on these shared runners depends on your
|
||||
- [Linux runners](build_cloud/linux_build_cloud.md).
|
||||
- [Windows runners](build_cloud/windows_build_cloud.md) (beta).
|
||||
- [macOS runners](build_cloud/macos_build_cloud.md) (beta).
|
||||
|
||||
The number of minutes you can use on these runners depends on your
|
||||
[quota](../../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota),
|
||||
which depends on your [subscription plan](../../subscriptions/gitlab_com/index.md#ci-pipeline-minutes).
|
||||
|
|
|
@ -33,13 +33,13 @@ If you are using a self-managed instance of GitLab:
|
|||
|
||||
If you are using GitLab.com:
|
||||
|
||||
- You can select from a list of [shared runners that GitLab maintains](../../user/gitlab_com/index.md#shared-runners).
|
||||
- You can select from a list of [shared runners that GitLab maintains](index.md).
|
||||
- The shared runners consume the [pipelines minutes](../../subscriptions/gitlab_com/index.md#ci-pipeline-minutes)
|
||||
included with your account.
|
||||
|
||||
### Enable shared runners
|
||||
|
||||
On GitLab.com, [shared runners](#shared-runners) are enabled in all projects by
|
||||
On GitLab.com, [shared runners](index.md) are enabled in all projects by
|
||||
default.
|
||||
|
||||
On self-managed instances of GitLab, an administrator must [install](https://docs.gitlab.com/runner/install/index.html)
|
||||
|
|
|
@ -536,7 +536,7 @@ You can use it either for personal or business websites, such as portfolios, doc
|
|||
- [Source](https://docs.gitlab.com/runner/)
|
||||
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/howto/runner.md)
|
||||
- Layer: Core Service (Processor)
|
||||
- GitLab.com: [Runner](../user/gitlab_com/index.md#shared-runners)
|
||||
- GitLab.com: [Runners](../ci/runners/index.md)
|
||||
|
||||
GitLab Runner runs jobs and sends the results to GitLab.
|
||||
|
||||
|
|
|
@ -66,12 +66,68 @@ To do that, you can use the `data` attributes in the HTML element and query them
|
|||
You should only do this while initializing the application, because the mounted element is replaced
|
||||
with a Vue-generated DOM.
|
||||
|
||||
The advantage of providing data from the DOM to the Vue instance through `props` in the `render`
|
||||
function instead of querying the DOM inside the main Vue component is avoiding the need to create a
|
||||
fixture or an HTML element in the unit test, which makes the tests easier.
|
||||
The advantage of providing data from the DOM to the Vue instance through `props` or
|
||||
`provide` in the `render` function, instead of querying the DOM inside the main Vue
|
||||
component, is that you avoid the need to create a fixture or an HTML element in the unit test.
|
||||
|
||||
See the following example. Also, please refer to our [Vue style guide](style/vue.md#basic-rules) for
|
||||
additional information on why we explicitly declare the data being passed into the Vue app;
|
||||
##### provide/inject
|
||||
|
||||
Vue supports dependency injection through [provide/inject](https://vuejs.org/v2/api/#provide-inject).
|
||||
Values passed to the component through `provide` can be accessed in the component the `inject` configuration.
|
||||
In the following example of a Vue app initialization, a value from HAML is passed to the component
|
||||
through the `provide` configuration:
|
||||
|
||||
```javascript
|
||||
#js-vue-app{ data: { endpoint: 'foo' }}
|
||||
|
||||
// index.js
|
||||
const el = document.getElementById('js-vue-app');
|
||||
|
||||
if (!el) return false;
|
||||
|
||||
const { endpoint } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(createElement) {
|
||||
return createElement('my-component', {
|
||||
provide: {
|
||||
endpoint
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The component, or any of its child components, can access the property through `inject` as:
|
||||
|
||||
```vue
|
||||
<script>
|
||||
export default {
|
||||
name: 'MyComponent',
|
||||
inject: ['endpoint'],
|
||||
...
|
||||
...
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
...
|
||||
...
|
||||
</template>
|
||||
```
|
||||
|
||||
Using dependency injection to provide values from HAML is ideal when:
|
||||
|
||||
- The injected value doesn't need an explicit validation against its data type or contents.
|
||||
- The value doesn't need to be reactive.
|
||||
- There are multiple components in the hierarchy that need access to this value where
|
||||
prop-drilling becomes an inconvenience. Prop-drilling when the same prop is passed
|
||||
through all components in the hierarchy until the component that is genuinely using it.
|
||||
|
||||
##### props
|
||||
|
||||
If the value from HAML doesn't fit the criteria of dependency injection, use `props`.
|
||||
See the following example.
|
||||
|
||||
```javascript
|
||||
// haml
|
||||
|
@ -99,6 +155,9 @@ return new Vue({
|
|||
> When adding an `id` attribute to mount a Vue application, please make sure this `id` is unique
|
||||
across the codebase.
|
||||
|
||||
For more information on why we explicitly declare the data being passed into the Vue app,
|
||||
refer to our [Vue style guide](style/vue.md#basic-rules).
|
||||
|
||||
#### Providing Rails form fields to Vue applications
|
||||
|
||||
When composing a form with Rails, the `name`, `id`, and `value` attributes of form inputs are generated
|
||||
|
|
|
@ -131,40 +131,70 @@ You can mark that content for translation with:
|
|||
|
||||
### JavaScript files
|
||||
|
||||
In JavaScript we added the `__()` (double underscore parenthesis) function that
|
||||
you can import from the `~/locale` file. For instance:
|
||||
|
||||
```javascript
|
||||
import { __ } from '~/locale';
|
||||
const label = __('Subscribe');
|
||||
```
|
||||
|
||||
To test JavaScript translations you must:
|
||||
|
||||
- Change the GitLab localization to a language other than English.
|
||||
- Generate JSON files by using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
|
||||
|
||||
### Vue files
|
||||
|
||||
In Vue files, we make the following functions available:
|
||||
The `~/locale` module exports the following key functions for externalization:
|
||||
|
||||
- `__()` (double underscore parenthesis)
|
||||
- `s__()` (namespaced double underscore parenthesis)
|
||||
|
||||
You can therefore import from the `~/locale` file.
|
||||
For example:
|
||||
- `__()` Mark content for translation (note the double underscore).
|
||||
- `s__()` Mark namespaced content for translation
|
||||
- `n__()` Mark pluralized content for translation
|
||||
|
||||
```javascript
|
||||
import { __, s__ } from '~/locale';
|
||||
import { __, s__, n__ } from '~/locale';
|
||||
|
||||
const defaultErrorMessage = s__('Branches|Create branch failed.');
|
||||
const label = __('Subscribe');
|
||||
const nameSpacedlabel = __('Plan|Subscribe');
|
||||
const message = n__('Apple', 'Apples', 3)
|
||||
```
|
||||
|
||||
For the static text strings we suggest two patterns for using these translations in Vue files:
|
||||
To test JavaScript translations, learn about [manually testing translations from the UI](#manually-test-translations-from-the-ui).
|
||||
|
||||
- External constants file:
|
||||
### Vue files
|
||||
|
||||
```javascript
|
||||
In Vue files, we make the following functions available to Vue templates using the `translate` mixin:
|
||||
|
||||
- `__()`
|
||||
- `s__()`
|
||||
- `n__()`
|
||||
- `sprintf`
|
||||
|
||||
This means you can externalize strings in Vue templates without having to import these functions from the `~/locale` file:
|
||||
|
||||
```html
|
||||
<template>
|
||||
<h1>{{ s__('Branches|Create a new branch') }}</h1>
|
||||
<gl-button>{{ __('Create branch') }}</gl-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
If you need to translate strings in the Vue component's JavaScript, you can import the necessary externalization function from the `~/locale` file as described in the [JavaScript files](#javascript-files) section.
|
||||
|
||||
To test Vue translations, learn about [manually testing translations from the UI](#manually-test-translations-from-the-ui).
|
||||
|
||||
#### Recommendations
|
||||
|
||||
If strings are reused throughout a component, it can be useful to define these strings as variables. We recommend defining an `i18n` property on the component's `$options` object. If there is a mixture of many-use and single-use strings in the component, consider using this approach to create a local [Single Source of Truth](https://about.gitlab.com/handbook/values/#single-source-of-truth) for externalized strings.
|
||||
|
||||
```javascript
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
buttonLabel: s__('Plan|Button Label')
|
||||
}
|
||||
},
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-button :aria-label="$options.i18n.buttonLabel">
|
||||
{{ $options.i18n.buttonLabel }}
|
||||
</gl-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
Also consider defining these strings in a `constants.js` file, especially if they need
|
||||
to be shared across different modules.
|
||||
|
||||
```javascript
|
||||
javascripts
|
||||
│
|
||||
└───alert_settings
|
||||
|
@ -179,60 +209,41 @@ For the static text strings we suggest two patterns for using these translations
|
|||
|
||||
/* Integration constants */
|
||||
|
||||
export const I18N_ALERT_SETTINGS_FORM = {
|
||||
saveBtnLabel: __('Save changes'),
|
||||
};
|
||||
export const MSG_ALERT_SETTINGS_FORM_ERROR = __('Failed to save alert settings.')
|
||||
|
||||
|
||||
// alert_settings_form.vue
|
||||
|
||||
import {
|
||||
I18N_ALERT_SETTINGS_FORM,
|
||||
MSG_ALERT_SETTINGS_FORM_ERROR,
|
||||
} from '../constants';
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
I18N_ALERT_SETTINGS_FORM,
|
||||
}
|
||||
MSG_ALERT_SETTINGS_FROM_ERROR,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-button
|
||||
ref="submitBtn"
|
||||
variant="success"
|
||||
type="submit"
|
||||
>
|
||||
{{ $options.i18n.I18N_ALERT_SETTINGS_FORM }}
|
||||
</gl-button>
|
||||
<gl-alert v-if="showAlert">
|
||||
{{ $options.MSG_ALERT_SETTINGS_FORM_ERROR }}
|
||||
</gl-alert>
|
||||
</template>
|
||||
```
|
||||
```
|
||||
|
||||
When possible, you should opt for this pattern, as this allows you to import these strings directly into your component specs for re-use during testing.
|
||||
Using either `constants` or `$options.i18n` allows us to reference messages directly in specs:
|
||||
|
||||
- Internal component `$options` object:
|
||||
```javascript
|
||||
import { MSG_ALERT_SETTINGS_FORM_ERROR } from 'path/to/constants.js';
|
||||
|
||||
```javascript
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
buttonLabel: s__('Plan|Button Label')
|
||||
}
|
||||
},
|
||||
</script>
|
||||
// okay
|
||||
expect(wrapper.text()).toEqual('this test will fail just from button text changing!');
|
||||
|
||||
<template>
|
||||
<gl-button :aria-label="$options.i18n.buttonLabel">
|
||||
{{ $options.i18n.buttonLabel }}
|
||||
</gl-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
To visually test the Vue translations:
|
||||
|
||||
1. Change the GitLab localization to another language than English.
|
||||
1. Generate JSON files using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
|
||||
// better
|
||||
expect(wrapper.text()).toEqual(MyComponent.i18n.buttonLabel);
|
||||
// also better
|
||||
expect(wrapper.text()).toEqual(MSG_ALERT_SETTINGS_FORM_ERROR);
|
||||
```
|
||||
|
||||
### Dynamic translations
|
||||
|
||||
|
@ -853,3 +864,10 @@ Suppose you want to add translations for a new language, for example, French:
|
|||
git add locale/fr/ app/assets/javascripts/locale/fr/
|
||||
git commit -m "Add French translations for Value Stream Analytics page"
|
||||
```
|
||||
|
||||
## Manually test translations from the UI
|
||||
|
||||
To manually test Vue translations:
|
||||
|
||||
1. Change the GitLab localization to another language than English.
|
||||
1. Generate JSON files using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
|
||||
|
|
|
@ -296,10 +296,10 @@ The GitLab Runner server requirements depend on:
|
|||
|
||||
Since the nature of the jobs varies for each use case, you need to experiment by adjusting the job concurrency to get the optimum setting.
|
||||
|
||||
For reference, GitLab.com's [auto-scaling shared runner](../user/gitlab_com/index.md#shared-runners) is configured so that a **single job** runs in a **single instance** with:
|
||||
For reference, the GitLab.com Build Cloud [auto-scaling runner for Linux](../ci/runners/build_cloud/linux_build_cloud.md) is configured so that a **single job** runs in a **single instance** with:
|
||||
|
||||
- 1vCPU.
|
||||
- 3.75GB of RAM.
|
||||
- 1 vCPU.
|
||||
- 3.75 GB of RAM.
|
||||
|
||||
## Supported web browsers
|
||||
|
||||
|
|
|
@ -177,11 +177,11 @@ The following limits apply for [Webhooks](../project/integrations/webhooks.md):
|
|||
| [Number of webhooks](../../administration/instance_limits.md#number-of-webhooks) | `100` per project, `50` per group | `100` per project, `50` per group |
|
||||
| Maximum payload size | 25 MB | 25 MB |
|
||||
|
||||
## Shared runners
|
||||
## Shared Build Cloud runners
|
||||
|
||||
GitLab has shared runners on GitLab.com that you can use to run your CI jobs.
|
||||
|
||||
For more information, see [choosing a runner](../../ci/runners/index.md).
|
||||
For more information, see [GitLab Build Cloud runners](../../ci/runners/index.md).
|
||||
|
||||
## Sidekiq
|
||||
|
||||
|
|
|
@ -83,6 +83,4 @@ arbitrary images as they effectively have root access.
|
|||
If you don't want to use a runner in privileged mode, either:
|
||||
|
||||
- Use shared runners on GitLab.com. They don't have this security issue.
|
||||
- Set up your own runners using the configuration described at
|
||||
[shared runners](../../gitlab_com/index.md#shared-runners) using
|
||||
[`docker+machine`](https://docs.gitlab.com/runner/executors/docker_machine.html).
|
||||
- Set up your own runners that use [`docker+machine`](https://docs.gitlab.com/runner/executors/docker_machine.html).
|
||||
|
|
|
@ -7,8 +7,6 @@ module Gitlab
|
|||
class Finding
|
||||
include ::VulnerabilityFindingHelpers
|
||||
|
||||
UNSAFE_SEVERITIES = %w[unknown high critical].freeze
|
||||
|
||||
attr_reader :compare_key
|
||||
attr_reader :confidence
|
||||
attr_reader :identifiers
|
||||
|
@ -86,8 +84,8 @@ module Gitlab
|
|||
@location = new_location
|
||||
end
|
||||
|
||||
def unsafe?
|
||||
severity.in?(UNSAFE_SEVERITIES)
|
||||
def unsafe?(severity_levels)
|
||||
severity.in?(severity_levels)
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
|
|
|
@ -22,8 +22,8 @@ module Gitlab
|
|||
reports.values.flat_map(&:findings)
|
||||
end
|
||||
|
||||
def violates_default_policy_against?(target_reports, vulnerabilities_allowed)
|
||||
unsafe_findings_count(target_reports) > vulnerabilities_allowed
|
||||
def violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels)
|
||||
unsafe_findings_count(target_reports, severity_levels) > vulnerabilities_allowed
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -32,8 +32,8 @@ module Gitlab
|
|||
findings - target_reports&.findings.to_a
|
||||
end
|
||||
|
||||
def unsafe_findings_count(target_reports)
|
||||
findings_diff(target_reports).count(&:unsafe?)
|
||||
def unsafe_findings_count(target_reports, severity_levels)
|
||||
findings_diff(target_reports).count {|finding| finding.unsafe?(severity_levels)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
"codesandbox-api": "0.0.23",
|
||||
"compression-webpack-plugin": "^5.0.2",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"core-js": "^3.16.0",
|
||||
"core-js": "^3.16.1",
|
||||
"cron-validator": "^1.1.1",
|
||||
"cropper": "^2.3.0",
|
||||
"css-loader": "^2.1.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { GlButton, GlModal } from '@gitlab/ui';
|
||||
import { GlButton, GlModal, GlDropdown } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { ApolloMutation } from 'vue-apollo';
|
||||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||
|
@ -8,8 +8,6 @@ import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
|
|||
import SnippetHeader from '~/snippets/components/snippet_header.vue';
|
||||
import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.graphql';
|
||||
|
||||
useMockLocationHelper();
|
||||
|
||||
describe('Snippet header component', () => {
|
||||
let wrapper;
|
||||
let snippet;
|
||||
|
@ -19,6 +17,7 @@ describe('Snippet header component', () => {
|
|||
let errorMsg;
|
||||
let err;
|
||||
const originalRelativeUrlRoot = gon.relative_url_root;
|
||||
const reportAbusePath = '/-/snippets/42/mark_as_spam';
|
||||
|
||||
const GlEmoji = { template: '<img/>' };
|
||||
|
||||
|
@ -27,6 +26,7 @@ describe('Snippet header component', () => {
|
|||
permissions = {},
|
||||
mutationRes = mutationTypes.RESOLVE,
|
||||
snippetProps = {},
|
||||
provide = {},
|
||||
} = {}) {
|
||||
const defaultProps = Object.assign(snippet, snippetProps);
|
||||
if (permissions) {
|
||||
|
@ -45,6 +45,10 @@ describe('Snippet header component', () => {
|
|||
|
||||
wrapper = mount(SnippetHeader, {
|
||||
mocks: { $apollo },
|
||||
provide: {
|
||||
reportAbusePath,
|
||||
...provide,
|
||||
},
|
||||
propsData: {
|
||||
snippet: {
|
||||
...defaultProps,
|
||||
|
@ -57,9 +61,27 @@ describe('Snippet header component', () => {
|
|||
});
|
||||
}
|
||||
|
||||
const findAuthorEmoji = () => wrapper.find(GlEmoji);
|
||||
const findAuthorEmoji = () => wrapper.findComponent(GlEmoji);
|
||||
const findAuthoredMessage = () => wrapper.find('[data-testid="authored-message"]').text();
|
||||
const buttonCount = () => wrapper.findAll(GlButton).length;
|
||||
const findButtons = () => wrapper.findAllComponents(GlButton);
|
||||
const findButtonsAsModel = () =>
|
||||
findButtons().wrappers.map((x) => ({
|
||||
text: x.text(),
|
||||
href: x.attributes('href'),
|
||||
category: x.props('category'),
|
||||
variant: x.props('variant'),
|
||||
disabled: x.props('disabled'),
|
||||
}));
|
||||
const findResponsiveDropdown = () => wrapper.findComponent(GlDropdown);
|
||||
// We can't search by component here since we are full mounting and the attributes are applied to a child of the GlDropdownItem
|
||||
const findResponsiveDropdownItems = () => findResponsiveDropdown().findAll('[role="menuitem"]');
|
||||
const findResponsiveDropdownItemsAsModel = () =>
|
||||
findResponsiveDropdownItems().wrappers.map((x) => ({
|
||||
disabled: x.attributes('disabled'),
|
||||
href: x.attributes('href'),
|
||||
title: x.attributes('title'),
|
||||
text: x.text(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
gon.relative_url_root = '/foo/';
|
||||
|
@ -144,42 +166,108 @@ describe('Snippet header component', () => {
|
|||
expect(text).toBe('Authored 1 month ago');
|
||||
});
|
||||
|
||||
it('renders action buttons based on permissions', () => {
|
||||
createComponent({
|
||||
permissions: {
|
||||
adminSnippet: false,
|
||||
updateSnippet: false,
|
||||
},
|
||||
});
|
||||
expect(buttonCount()).toEqual(0);
|
||||
it('renders a action buttons', () => {
|
||||
createComponent();
|
||||
|
||||
createComponent({
|
||||
permissions: {
|
||||
adminSnippet: true,
|
||||
updateSnippet: false,
|
||||
expect(findButtonsAsModel()).toEqual([
|
||||
{
|
||||
category: 'primary',
|
||||
disabled: false,
|
||||
href: `${snippet.webUrl}/edit`,
|
||||
text: 'Edit',
|
||||
variant: 'default',
|
||||
},
|
||||
});
|
||||
expect(buttonCount()).toEqual(1);
|
||||
{
|
||||
category: 'secondary',
|
||||
disabled: false,
|
||||
text: 'Delete',
|
||||
variant: 'danger',
|
||||
},
|
||||
{
|
||||
category: 'primary',
|
||||
disabled: false,
|
||||
href: reportAbusePath,
|
||||
text: 'Submit as spam',
|
||||
variant: 'default',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
createComponent({
|
||||
permissions: {
|
||||
adminSnippet: true,
|
||||
updateSnippet: true,
|
||||
},
|
||||
});
|
||||
expect(buttonCount()).toEqual(2);
|
||||
it('renders responsive dropdown for action buttons', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findResponsiveDropdownItemsAsModel()).toEqual([
|
||||
{
|
||||
href: `${snippet.webUrl}/edit`,
|
||||
text: 'Edit',
|
||||
},
|
||||
{
|
||||
text: 'Delete',
|
||||
},
|
||||
{
|
||||
href: reportAbusePath,
|
||||
text: 'Submit as spam',
|
||||
title: 'Submit as spam',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each`
|
||||
permissions | buttons
|
||||
${{ adminSnippet: false, updateSnippet: false }} | ${['Submit as spam']}
|
||||
${{ adminSnippet: true, updateSnippet: false }} | ${['Delete', 'Submit as spam']}
|
||||
${{ adminSnippet: false, updateSnippet: true }} | ${['Edit', 'Submit as spam']}
|
||||
`('with permissions ($permissions), renders buttons ($buttons)', ({ permissions, buttons }) => {
|
||||
createComponent({
|
||||
permissions: {
|
||||
adminSnippet: true,
|
||||
updateSnippet: true,
|
||||
...permissions,
|
||||
},
|
||||
});
|
||||
wrapper.setData({
|
||||
canCreateSnippet: true,
|
||||
|
||||
expect(findButtonsAsModel().map((x) => x.text)).toEqual(buttons);
|
||||
});
|
||||
|
||||
it('with canCreateSnippet permission, renders create button', async () => {
|
||||
createComponent();
|
||||
|
||||
// TODO: we should avoid `wrapper.setData` since they
|
||||
// are component internals. Let's use the apollo mock helpers
|
||||
// in a follow-up.
|
||||
wrapper.setData({ canCreateSnippet: true });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findButtonsAsModel()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
category: 'secondary',
|
||||
disabled: false,
|
||||
href: `/foo/-/snippets/new`,
|
||||
text: 'New snippet',
|
||||
variant: 'success',
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
describe('with guest user', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
permissions: {
|
||||
adminSnippet: false,
|
||||
updateSnippet: false,
|
||||
},
|
||||
provide: {
|
||||
reportAbusePath: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(buttonCount()).toEqual(3);
|
||||
|
||||
it('does not show any action buttons', () => {
|
||||
expect(findButtons()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not show responsive action dropdown', () => {
|
||||
expect(findResponsiveDropdown().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -221,6 +309,8 @@ describe('Snippet header component', () => {
|
|||
});
|
||||
|
||||
describe('in case of successful mutation, closes modal and redirects to correct listing', () => {
|
||||
useMockLocationHelper();
|
||||
|
||||
const createDeleteSnippet = (snippetProps = {}) => {
|
||||
createComponent({
|
||||
snippetProps,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue';
|
||||
|
||||
|
@ -26,7 +25,7 @@ describe('toolbar_button', () => {
|
|||
});
|
||||
|
||||
const getButtonShortcutsAttr = () => {
|
||||
return wrapper.find(GlButton).attributes('data-md-shortcuts');
|
||||
return wrapper.find('button').attributes('data-md-shortcuts');
|
||||
};
|
||||
|
||||
describe('keyboard shortcuts', () => {
|
||||
|
|
|
@ -92,4 +92,23 @@ RSpec.describe SnippetsHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#snippet_report_abuse_path' do
|
||||
let(:snippet) { public_personal_snippet }
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
subject { snippet_report_abuse_path(snippet) }
|
||||
|
||||
it 'returns false if the user cannot submit the snippet as spam' do
|
||||
allow(snippet).to receive(:submittable_as_spam_by?).and_return(false)
|
||||
|
||||
expect(subject).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns true if the user can submit the snippet as spam' do
|
||||
allow(snippet).to receive(:submittable_as_spam_by?).and_return(true)
|
||||
|
||||
expect(subject).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,27 +54,25 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
|
|||
end
|
||||
|
||||
describe "#violates_default_policy_against?" do
|
||||
let(:low_severity_sast) { build(:ci_reports_security_finding, severity: 'low', report_type: :sast) }
|
||||
let(:high_severity_dast) { build(:ci_reports_security_finding, severity: 'high', report_type: :dast) }
|
||||
let(:vulnerabilities_allowed) { 0 }
|
||||
let(:severity_levels) { %w(critical high) }
|
||||
|
||||
subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed) }
|
||||
subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels) }
|
||||
|
||||
before do
|
||||
security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
|
||||
end
|
||||
|
||||
context 'when the target_reports is `nil`' do
|
||||
let(:target_reports) { nil }
|
||||
|
||||
context "when a report has unsafe vulnerability" do
|
||||
before do
|
||||
security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
|
||||
end
|
||||
|
||||
context 'with severity levels matching the existing vulnerabilities' do
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context "when none of the reports have an unsafe vulnerability" do
|
||||
before do
|
||||
security_reports.get_report('sast', artifact).add_finding(low_severity_sast)
|
||||
end
|
||||
context "without any severity levels matching the existing vulnerabilities" do
|
||||
let(:severity_levels) { %w(critical) }
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
@ -84,10 +82,8 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
|
|||
let(:target_reports) { described_class.new(pipeline) }
|
||||
|
||||
context "when a report has a new unsafe vulnerability" do
|
||||
before do
|
||||
security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
|
||||
security_reports.get_report('dependency_scanning', artifact).add_finding(low_severity_sast)
|
||||
target_reports.get_report('dependency_scanning', artifact).add_finding(low_severity_sast)
|
||||
context 'with severity levels matching the existing vulnerabilities' do
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
|
@ -97,12 +93,16 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
|
|||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
context "without any severity levels matching the existing vulnerabilities" do
|
||||
let(:severity_levels) { %w(critical) }
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
context "when none of the reports have a new unsafe vulnerability" do
|
||||
before do
|
||||
security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
|
||||
security_reports.get_report('sast', artifact).add_finding(low_severity_sast)
|
||||
target_reports.get_report('sast', artifact).add_finding(high_severity_dast)
|
||||
end
|
||||
|
||||
|
|
|
@ -3665,10 +3665,10 @@ core-js-pure@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
|
||||
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
|
||||
|
||||
core-js@^3.1.3, core-js@^3.16.0:
|
||||
version "3.16.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.16.0.tgz#1d46fb33720bc1fa7f90d20431f36a5540858986"
|
||||
integrity sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==
|
||||
core-js@^3.1.3, core-js@^3.16.1:
|
||||
version "3.16.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.16.1.tgz#f4485ce5c9f3c6a7cb18fa80488e08d362097249"
|
||||
integrity sha512-AAkP8i35EbefU+JddyWi12AWE9f2N/qr/pwnDtWz4nyUIBGMJPX99ANFFRSw6FefM374lDujdtLDyhN2A/btHw==
|
||||
|
||||
core-js@~2.3.0:
|
||||
version "2.3.0"
|
||||
|
|
Loading…
Reference in New Issue