Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4ba8ae9707
commit
534ce3b2d0
|
|
@ -994,7 +994,7 @@ Gemspec/AvoidExecutingGit:
|
|||
|
||||
Lint/BinaryOperatorWithIdenticalOperands:
|
||||
Exclude:
|
||||
- '{,ee/,qa/}spec/**/*_{spec,shared_examples,shared_context}.rb'
|
||||
- '{,ee/,qa/,jh/}spec/**/*_{spec,shared_examples,shared_context}.rb'
|
||||
|
||||
Cop/SidekiqRedisCall:
|
||||
Enabled: true
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ PATH
|
|||
remote: gems/gitlab-safe_request_store
|
||||
specs:
|
||||
gitlab-safe_request_store (0.1.0)
|
||||
rack (~> 2.2.8)
|
||||
request_store
|
||||
|
||||
PATH
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
let hasPlainText;
|
||||
|
||||
formTextarea.wrap('<div class="div-dropzone"></div>');
|
||||
formTextarea.on('paste', (event) => handlePaste(event));
|
||||
|
||||
// Add dropzone area to the form.
|
||||
const $mdArea = formTextarea.closest('.md-area');
|
||||
|
|
@ -60,6 +59,8 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
formTextarea.on('paste', (event) => handlePaste(event));
|
||||
|
||||
const dropzone = $formDropzone.dropzone({
|
||||
url: uploadsPath,
|
||||
dictDefaultMessage: '',
|
||||
|
|
|
|||
|
|
@ -42,7 +42,12 @@ export default {
|
|||
} = await this.$apollo.mutate({
|
||||
mutation: organizationCreateMutation,
|
||||
variables: {
|
||||
input: { name: formValues.name, path: formValues.path, avatar: formValues.avatar },
|
||||
input: {
|
||||
name: formValues.name,
|
||||
path: formValues.path,
|
||||
description: formValues.description,
|
||||
avatar: formValues.avatar,
|
||||
},
|
||||
},
|
||||
context: {
|
||||
hasUpload: formValues.avatar instanceof File,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ export const initOrganizationsNew = () => {
|
|||
const {
|
||||
dataset: { appData },
|
||||
} = el;
|
||||
const { organizationsPath, rootUrl } = convertObjectPropsToCamelCase(JSON.parse(appData));
|
||||
const { organizationsPath, rootUrl, previewMarkdownPath } = convertObjectPropsToCamelCase(
|
||||
JSON.parse(appData),
|
||||
);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
|
|
@ -26,6 +28,7 @@ export const initOrganizationsNew = () => {
|
|||
provide: {
|
||||
organizationsPath,
|
||||
rootUrl,
|
||||
previewMarkdownPath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(App);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import NewEditForm from '~/organizations/shared/components/new_edit_form.vue';
|
|||
import {
|
||||
FORM_FIELD_NAME,
|
||||
FORM_FIELD_ID,
|
||||
FORM_FIELD_DESCRIPTION,
|
||||
FORM_FIELD_AVATAR,
|
||||
} from '~/organizations/shared/constants';
|
||||
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
||||
|
|
@ -29,7 +30,7 @@ export default {
|
|||
),
|
||||
successMessage: s__('Organization|Organization was successfully updated.'),
|
||||
},
|
||||
fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_AVATAR],
|
||||
fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_DESCRIPTION, FORM_FIELD_AVATAR],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
|
@ -66,6 +67,7 @@ export default {
|
|||
input: {
|
||||
id: convertToGraphQLId(TYPE_ORGANIZATION, this.organization.id),
|
||||
name: formValues.name,
|
||||
description: formValues.description,
|
||||
...this.avatarInput(formValues),
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ export const initOrganizationsSettingsGeneral = () => {
|
|||
const {
|
||||
dataset: { appData },
|
||||
} = el;
|
||||
const { organization, organizationsPath, rootUrl } = convertObjectPropsToCamelCase(
|
||||
JSON.parse(appData),
|
||||
);
|
||||
const {
|
||||
organization,
|
||||
organizationsPath,
|
||||
rootUrl,
|
||||
previewMarkdownPath,
|
||||
} = convertObjectPropsToCamelCase(JSON.parse(appData));
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
|
|
@ -29,6 +32,7 @@ export const initOrganizationsSettingsGeneral = () => {
|
|||
organization,
|
||||
organizationsPath,
|
||||
rootUrl,
|
||||
previewMarkdownPath,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(App);
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import { formValidators } from '@gitlab/ui/dist/utils';
|
|||
import { s__, __ } from '~/locale';
|
||||
import { slugify } from '~/lib/utils/text_utility';
|
||||
import AvatarUploadDropzone from '~/vue_shared/components/upload_dropzone/avatar_upload_dropzone.vue';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import {
|
||||
FORM_FIELD_NAME,
|
||||
FORM_FIELD_ID,
|
||||
FORM_FIELD_PATH,
|
||||
FORM_FIELD_DESCRIPTION,
|
||||
FORM_FIELD_AVATAR,
|
||||
FORM_FIELD_PATH_VALIDATORS,
|
||||
} from '../constants';
|
||||
|
|
@ -21,12 +24,27 @@ export default {
|
|||
GlButton,
|
||||
OrganizationUrlField,
|
||||
AvatarUploadDropzone,
|
||||
MarkdownField,
|
||||
},
|
||||
i18n: {
|
||||
cancel: __('Cancel'),
|
||||
},
|
||||
formId: 'new-organization-form',
|
||||
inject: ['organizationsPath'],
|
||||
markdownDocsPath: helpPagePath('user/organization/index', {
|
||||
anchor: 'organization-description-supported-markdown',
|
||||
}),
|
||||
restrictedToolBarItems: [
|
||||
'code',
|
||||
'quote',
|
||||
'bullet-list',
|
||||
'numbered-list',
|
||||
'task-list',
|
||||
'collapsible-section',
|
||||
'table',
|
||||
'attach-file',
|
||||
'full-screen',
|
||||
],
|
||||
inject: ['organizationsPath', 'previewMarkdownPath'],
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
|
@ -39,6 +57,7 @@ export default {
|
|||
return {
|
||||
[FORM_FIELD_NAME]: '',
|
||||
[FORM_FIELD_PATH]: '',
|
||||
[FORM_FIELD_DESCRIPTION]: '',
|
||||
[FORM_FIELD_AVATAR]: null,
|
||||
};
|
||||
},
|
||||
|
|
@ -47,7 +66,7 @@ export default {
|
|||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return [FORM_FIELD_NAME, FORM_FIELD_PATH, FORM_FIELD_AVATAR];
|
||||
return [FORM_FIELD_NAME, FORM_FIELD_PATH, FORM_FIELD_DESCRIPTION, FORM_FIELD_AVATAR];
|
||||
},
|
||||
},
|
||||
submitButtonText: {
|
||||
|
|
@ -102,6 +121,12 @@ export default {
|
|||
class: 'gl-w-full',
|
||||
},
|
||||
},
|
||||
[FORM_FIELD_DESCRIPTION]: {
|
||||
label: s__('Organization|Organization description (optional)'),
|
||||
groupAttrs: {
|
||||
class: 'gl-w-full common-note-form',
|
||||
},
|
||||
},
|
||||
[FORM_FIELD_AVATAR]: {
|
||||
label: s__('Organization|Organization avatar'),
|
||||
groupAttrs: {
|
||||
|
|
@ -137,6 +162,7 @@ export default {
|
|||
formFieldsInputEvent(event);
|
||||
this.hasPathBeenManuallySet = true;
|
||||
},
|
||||
helpPagePath,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -159,6 +185,28 @@ export default {
|
|||
@blur="blur"
|
||||
/>
|
||||
</template>
|
||||
<template #input(description)="{ id, value, input, blur }">
|
||||
<div class="gl-md-form-input-xl">
|
||||
<markdown-field
|
||||
:can-attach-file="false"
|
||||
:markdown-preview-path="previewMarkdownPath"
|
||||
:markdown-docs-path="$options.markdownDocsPath"
|
||||
:textarea-value="value"
|
||||
:restricted-tool-bar-items="$options.restrictedToolBarItems"
|
||||
>
|
||||
<template #textarea>
|
||||
<textarea
|
||||
:id="id"
|
||||
:value="value"
|
||||
class="note-textarea js-gfm-input markdown-area"
|
||||
maxlength="1024"
|
||||
@input="input($event.target.value)"
|
||||
@blur="blur"
|
||||
></textarea>
|
||||
</template>
|
||||
</markdown-field>
|
||||
</div>
|
||||
</template>
|
||||
<template #input(avatar)="{ input, value }">
|
||||
<avatar-upload-dropzone
|
||||
:value="value"
|
||||
|
|
@ -169,9 +217,14 @@ export default {
|
|||
</template>
|
||||
</gl-form-fields>
|
||||
<div class="gl-display-flex gl-gap-3">
|
||||
<gl-button type="submit" variant="confirm" class="js-no-auto-disable" :loading="loading">{{
|
||||
submitButtonText
|
||||
}}</gl-button>
|
||||
<gl-button
|
||||
type="submit"
|
||||
variant="confirm"
|
||||
class="js-no-auto-disable"
|
||||
:loading="loading"
|
||||
data-testid="submit-button"
|
||||
>{{ submitButtonText }}</gl-button
|
||||
>
|
||||
<gl-button v-if="showCancelButton" :href="organizationsPath">{{
|
||||
$options.i18n.cancel
|
||||
}}</gl-button>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-form-input-group>
|
||||
<gl-form-input-group class="gl-md-form-input-xl">
|
||||
<template #prepend>
|
||||
<gl-input-group-text class="organization-root-path">
|
||||
<gl-truncate :text="baseUrl" position="middle" />
|
||||
|
|
@ -50,7 +50,7 @@ export default {
|
|||
:id="id"
|
||||
:value="value"
|
||||
:placeholder="$options.i18n.pathPlaceholder"
|
||||
class="gl-h-auto! gl-md-form-input-lg"
|
||||
class="gl-h-auto!"
|
||||
@input="$emit('input', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { s__ } from '~/locale';
|
|||
export const FORM_FIELD_NAME = 'name';
|
||||
export const FORM_FIELD_ID = 'id';
|
||||
export const FORM_FIELD_PATH = 'path';
|
||||
export const FORM_FIELD_DESCRIPTION = 'description';
|
||||
export const FORM_FIELD_AVATAR = 'avatar';
|
||||
|
||||
export const FORM_FIELD_PATH_VALIDATORS = [
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<script>
|
||||
import OrganizationAvatar from './organization_avatar.vue';
|
||||
import OrganizationDescription from './organization_description.vue';
|
||||
import GroupsAndProjects from './groups_and_projects.vue';
|
||||
import AssociationCounts from './association_counts.vue';
|
||||
|
||||
export default {
|
||||
name: 'OrganizationShowApp',
|
||||
components: { OrganizationAvatar, GroupsAndProjects, AssociationCounts },
|
||||
components: { OrganizationAvatar, OrganizationDescription, GroupsAndProjects, AssociationCounts },
|
||||
props: {
|
||||
organization: {
|
||||
type: Object,
|
||||
|
|
@ -26,6 +27,7 @@ export default {
|
|||
<template>
|
||||
<div class="gl-py-6">
|
||||
<organization-avatar :organization="organization" />
|
||||
<organization-description :organization="organization" />
|
||||
<association-counts
|
||||
:association-counts="associationCounts"
|
||||
:groups-and-projects-organization-path="groupsAndProjectsOrganizationPath"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<script>
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
|
||||
export default {
|
||||
name: 'OrganizationDescription',
|
||||
directives: {
|
||||
SafeHtml,
|
||||
},
|
||||
props: {
|
||||
organization: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="organization.description_html"
|
||||
v-safe-html="organization.description_html"
|
||||
class="gl-mt-5 md"
|
||||
></div>
|
||||
</template>
|
||||
|
|
@ -382,6 +382,7 @@ export default {
|
|||
@click="handleQuote"
|
||||
/>
|
||||
<toolbar-button
|
||||
v-if="!restrictedToolBarItems.includes('code')"
|
||||
v-show="!previewMarkdown"
|
||||
tag="`"
|
||||
tag-block="```"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,5 @@
|
|||
// JH-only stylesheets
|
||||
@import 'application_jh';
|
||||
|
||||
/* print styles */
|
||||
@media print {
|
||||
@import 'print';
|
||||
}
|
||||
// print styles
|
||||
@import 'print';
|
||||
|
|
|
|||
|
|
@ -7176,7 +7176,7 @@
|
|||
}
|
||||
|
||||
.emoji-icon {
|
||||
background-image: image-url('emoji.png');
|
||||
background-image: url('emoji.png');
|
||||
background-repeat: no-repeat;
|
||||
color: transparent;
|
||||
text-indent: -99em;
|
||||
|
|
@ -7190,7 +7190,7 @@
|
|||
only screen and (min-device-pixel-ratio: 2),
|
||||
only screen and (min-resolution: 192dpi),
|
||||
only screen and (min-resolution: 2dppx) {
|
||||
background-image: image-url('emoji@2x.png');
|
||||
background-image: url('emoji@2x.png');
|
||||
background-size: 860px 840px;
|
||||
}
|
||||
/* stylelint-enable media-feature-name-no-vendor-prefix */
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Usage:
|
|||
font-style: normal;
|
||||
/* stylelint-disable-next-line property-no-unknown */
|
||||
font-named-instance: 'Regular';
|
||||
src: font-url('gitlab-sans/GitLabSans.woff2') format('woff2');
|
||||
src: url('gitlab-sans/GitLabSans.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -21,7 +21,7 @@ Usage:
|
|||
font-style: italic;
|
||||
/* stylelint-disable-next-line property-no-unknown */
|
||||
font-named-instance: 'Regular';
|
||||
src: font-url('gitlab-sans/GitLabSans-Italic.woff2') format('woff2');
|
||||
src: url('gitlab-sans/GitLabSans-Italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------
|
||||
|
|
@ -35,7 +35,7 @@ Usage:
|
|||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
src: font-url('gitlab-mono/GitLabMono.woff2') format('woff2');
|
||||
src: url('gitlab-mono/GitLabMono.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -43,7 +43,7 @@ Usage:
|
|||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
src: font-url('gitlab-mono/GitLabMono-Italic.woff2') format('woff2');
|
||||
src: url('gitlab-mono/GitLabMono-Italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
// This isn't the best solution, but we needed a quick fix
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ $diff-file-header: 41px;
|
|||
width: 15px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
background: image-url('swipemode_sprites.gif') 0 3px no-repeat;
|
||||
background: url('swipemode_sprites.gif') 0 3px no-repeat;
|
||||
}
|
||||
|
||||
.bottom-handle {
|
||||
|
|
@ -234,7 +234,7 @@ $diff-file-header: 41px;
|
|||
width: 15px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background: image-url('swipemode_sprites.gif') 0 -11px no-repeat;
|
||||
background: url('swipemode_sprites.gif') 0 -11px no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +272,7 @@ $diff-file-header: 41px;
|
|||
left: 12px;
|
||||
height: 10px;
|
||||
width: 276px;
|
||||
background: image-url('onion_skin_sprites.gif') -4px -20px repeat-x;
|
||||
background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
|
||||
}
|
||||
|
||||
.dragger {
|
||||
|
|
@ -282,7 +282,7 @@ $diff-file-header: 41px;
|
|||
top: 0;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background: image-url('onion_skin_sprites.gif') 0 -34px repeat-x;
|
||||
background: url('onion_skin_sprites.gif') 0 -34px repeat-x;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +293,7 @@ $diff-file-header: 41px;
|
|||
right: 0;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background: image-url('onion_skin_sprites.gif') -2px 0 no-repeat;
|
||||
background: url('onion_skin_sprites.gif') -2px 0 no-repeat;
|
||||
}
|
||||
|
||||
.opaque {
|
||||
|
|
@ -303,7 +303,7 @@ $diff-file-header: 41px;
|
|||
left: 0;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background: image-url('onion_skin_sprites.gif') -2px -10px no-repeat;
|
||||
background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -770,12 +770,12 @@ table.code {
|
|||
.frame.click-to-comment,
|
||||
.btn-transparent.image-diff-overlay-add-comment {
|
||||
position: relative;
|
||||
cursor: image-url('illustrations/image_comment_light_cursor.svg') $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
|
||||
cursor: url('illustrations/image_comment_light_cursor.svg') $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
|
||||
auto;
|
||||
|
||||
// Retina cursor
|
||||
cursor: image-set(image-url('illustrations/image_comment_light_cursor.svg') 1x,
|
||||
image-url('illustrations/image_comment_light_cursor@2x.svg') 2x) $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
|
||||
cursor: image-set(url('illustrations/image_comment_light_cursor.svg') 1x,
|
||||
url('illustrations/image_comment_light_cursor@2x.svg') 2x) $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
|
||||
auto;
|
||||
|
||||
.comment-indicator {
|
||||
|
|
|
|||
|
|
@ -575,7 +575,7 @@
|
|||
left: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
mask-image: asset_url('icons-stacked.svg#check');
|
||||
mask-image: url('icons-stacked.svg#check');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-position: center center;
|
||||
|
|
|
|||
|
|
@ -420,7 +420,7 @@ span.idiff {
|
|||
@include gl-h-5;
|
||||
@include gl-float-left;
|
||||
background-color: $gray-400;
|
||||
mask-image: asset_url('icons-stacked.svg#doc-versions');
|
||||
mask-image: url('icons-stacked.svg#doc-versions');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-position: center;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
@include gl-mr-2;
|
||||
@include gl-w-4;
|
||||
@include gl-h-4;
|
||||
mask-image: asset_url('icons-stacked.svg#link');
|
||||
mask-image: url('icons-stacked.svg#link');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-position: center;
|
||||
|
|
|
|||
|
|
@ -495,7 +495,7 @@
|
|||
|
||||
&::after {
|
||||
@include gl-dark-invert-keep-hue;
|
||||
content: image-url('icon_anchor.svg');
|
||||
content: url('icon_anchor.svg');
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@
|
|||
@include gl-w-5;
|
||||
@include gl-h-5;
|
||||
background-color: rgba($color, 0.3);
|
||||
mask-image: asset_url('icons-stacked.svg##{$icon}');
|
||||
mask-image: url('icons-stacked.svg##{$icon}');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-position: center;
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ ul.related-merge-requests > li gl-emoji {
|
|||
|
||||
&::after {
|
||||
@include gl-dark-invert-keep-hue;
|
||||
content: image-url('icon_anchor.svg');
|
||||
content: url('icon_anchor.svg');
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -966,7 +966,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
|
|||
.unified-diff-components-diff-note-button {
|
||||
&::before {
|
||||
background-color: $blue-500;
|
||||
mask-image: asset_url('icons-stacked.svg#comment');
|
||||
mask-image: url('icons-stacked.svg#comment');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-position: center;
|
||||
|
|
|
|||
|
|
@ -4,97 +4,99 @@
|
|||
@import '@gitlab/ui/src/scss/variables';
|
||||
@import '@gitlab/ui/src/scss/utility-mixins/index';
|
||||
|
||||
.md h1,
|
||||
.md h2,
|
||||
.md h3,
|
||||
.md h4,
|
||||
.md h5,
|
||||
.md h6 {
|
||||
margin-top: 17px;
|
||||
}
|
||||
@media print {
|
||||
.md h1,
|
||||
.md h2,
|
||||
.md h3,
|
||||
.md h4,
|
||||
.md h5,
|
||||
.md h6 {
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.md h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
.md h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.md h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
.md h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.md h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.md h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.md {
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
.md {
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
|
||||
// fix blockquote style in print
|
||||
blockquote {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -4px;
|
||||
content: ' ';
|
||||
height: 100%;
|
||||
width: 4px;
|
||||
background-color: $gray-100;
|
||||
// fix blockquote style in print
|
||||
blockquote {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -4px;
|
||||
content: ' ';
|
||||
height: 100%;
|
||||
width: 4px;
|
||||
background-color: $gray-100;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
@include gl-text-gray-700;
|
||||
@include gl-py-3;
|
||||
@include gl-pl-6;
|
||||
@include gl-my-3;
|
||||
@include gl-mx-0;
|
||||
@include gl-inset-border-l-4-gray-100;
|
||||
margin-left: 4px;
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
@include gl-text-gray-700;
|
||||
@include gl-py-3;
|
||||
@include gl-pl-6;
|
||||
@include gl-my-3;
|
||||
@include gl-mx-0;
|
||||
@include gl-inset-border-l-4-gray-100;
|
||||
margin-left: 4px;
|
||||
border: 0 !important;
|
||||
header,
|
||||
nav,
|
||||
.nav-sidebar,
|
||||
.super-sidebar,
|
||||
.profiler-results,
|
||||
.tree-ref-holder,
|
||||
.tree-holder .breadcrumb,
|
||||
.nav,
|
||||
.btn,
|
||||
ul.notes-form,
|
||||
.issuable-gutter-toggle,
|
||||
.gutter-toggle,
|
||||
.issuable-details .content-block-small,
|
||||
.edit-link,
|
||||
.note-action-button,
|
||||
.right-sidebar,
|
||||
.flash-container,
|
||||
copy-code,
|
||||
#js-peek {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
page-break-before: avoid;
|
||||
page-break-inside: auto;
|
||||
}
|
||||
|
||||
.page-gutter {
|
||||
padding-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
a[href]::after {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.with-performance-bar .layout-page {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
header,
|
||||
nav,
|
||||
.nav-sidebar,
|
||||
.super-sidebar,
|
||||
.profiler-results,
|
||||
.tree-ref-holder,
|
||||
.tree-holder .breadcrumb,
|
||||
.nav,
|
||||
.btn,
|
||||
ul.notes-form,
|
||||
.issuable-gutter-toggle,
|
||||
.gutter-toggle,
|
||||
.issuable-details .content-block-small,
|
||||
.edit-link,
|
||||
.note-action-button,
|
||||
.right-sidebar,
|
||||
.flash-container,
|
||||
copy-code,
|
||||
#js-peek {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
page-break-before: avoid;
|
||||
page-break-inside: auto;
|
||||
}
|
||||
|
||||
.page-gutter {
|
||||
padding-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
a[href]::after {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.with-performance-bar .layout-page {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@
|
|||
|
||||
.gl-snippet-icon {
|
||||
display: inline-block;
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(asset_path('ext_snippet_icons/ext_snippet_icons.png')) no-repeat;
|
||||
background: url('ext_snippet_icons/ext_snippet_icons.png') no-repeat;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
width: 16px;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ module PreviewMarkdown
|
|||
when 'groups' then { group: group, issuable_reference_expansion_enabled: true }
|
||||
when 'projects' then projects_filter_params
|
||||
when 'timeline_events' then timeline_events_filter_params
|
||||
when 'organizations' then { pipeline: :description }
|
||||
else {}
|
||||
end.merge(
|
||||
requested_path: params[:path],
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
module Organizations
|
||||
class OrganizationsController < ApplicationController
|
||||
include PreviewMarkdown
|
||||
|
||||
feature_category :cell
|
||||
|
||||
skip_before_action :authenticate_user!, except: [:index, :new, :users]
|
||||
skip_before_action :authenticate_user!, only: [:show, :groups_and_projects]
|
||||
|
||||
def index; end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,13 @@ module Ci
|
|||
end
|
||||
|
||||
def execute
|
||||
items = @runner.builds
|
||||
items = if params[:system_id].blank?
|
||||
runner.builds
|
||||
else
|
||||
runner_manager = Ci::RunnerManager.for_runner(runner).with_system_xid(params[:system_id]).first
|
||||
Ci::Build.belonging_to_runner_manager(runner_manager&.id)
|
||||
end
|
||||
|
||||
items = by_permission(items)
|
||||
items = by_status(items)
|
||||
sort_items(items)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ module Organizations
|
|||
module OrganizationHelper
|
||||
def organization_show_app_data(organization)
|
||||
{
|
||||
organization: organization.slice(:id, :name).merge({ avatar_url: organization.avatar_url(size: 128) }),
|
||||
organization: organization.slice(:id, :name, :description_html)
|
||||
.merge({ avatar_url: organization.avatar_url(size: 128) }),
|
||||
groups_and_projects_organization_path: groups_and_projects_organization_path(organization),
|
||||
# TODO: Update counts to use real data
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/424531
|
||||
|
|
@ -17,18 +18,14 @@ module Organizations
|
|||
end
|
||||
|
||||
def organization_new_app_data
|
||||
{
|
||||
organizations_path: organizations_path,
|
||||
root_url: root_url
|
||||
}.to_json
|
||||
shared_new_settings_general_app_data.to_json
|
||||
end
|
||||
|
||||
def organization_settings_general_app_data(organization)
|
||||
{
|
||||
organization: organization.slice(:id, :name, :path).merge({ avatar: organization.avatar_url(size: 192) }),
|
||||
organizations_path: organizations_path,
|
||||
root_url: root_url
|
||||
}.to_json
|
||||
organization: organization.slice(:id, :name, :path, :description)
|
||||
.merge({ avatar: organization.avatar_url(size: 192) })
|
||||
}.merge(shared_new_settings_general_app_data).to_json
|
||||
end
|
||||
|
||||
def organization_groups_and_projects_app_data
|
||||
|
|
@ -66,6 +63,14 @@ module Organizations
|
|||
}
|
||||
end
|
||||
|
||||
def shared_new_settings_general_app_data
|
||||
{
|
||||
preview_markdown_path: preview_markdown_organizations_path,
|
||||
organizations_path: organizations_path,
|
||||
root_url: root_url
|
||||
}
|
||||
end
|
||||
|
||||
# See UsersHelper#admin_users_paths for inspiration to this method
|
||||
def organizations_users_paths
|
||||
{
|
||||
|
|
|
|||
|
|
@ -190,6 +190,10 @@ module Ci
|
|||
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123131
|
||||
scope :with_runner_type, -> (runner_type) { joins(:runner).where(runner: { runner_type: runner_type }) }
|
||||
|
||||
scope :belonging_to_runner_manager, -> (runner_machine_id) {
|
||||
joins(:runner_manager_build).where(p_ci_runner_machine_builds: { runner_machine_id: runner_machine_id })
|
||||
}
|
||||
|
||||
scope :with_secure_reports_from_config_options, -> (job_types) do
|
||||
joins(:metadata).where("#{Ci::BuildMetadata.quoted_table_name}.config_options -> 'artifacts' -> 'reports' ?| array[:job_types]", job_types: job_types)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ module Ci
|
|||
where(runner_id: runner_id)
|
||||
end
|
||||
|
||||
scope :with_system_xid, ->(system_xid) do
|
||||
where(system_xid: system_xid)
|
||||
end
|
||||
|
||||
scope :with_running_builds, -> do
|
||||
where('EXISTS(?)',
|
||||
Ci::Build.select(1)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ module Organizations
|
|||
'organizations/path': true,
|
||||
length: { minimum: 2, maximum: 255 }
|
||||
|
||||
delegate :description, :avatar, :avatar_url, :remove_avatar!, to: :organization_detail
|
||||
delegate :description, :description_html, :avatar, :avatar_url, :remove_avatar!, to: :organization_detail
|
||||
|
||||
accepts_nested_attributes_for :organization_detail
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module Organizations
|
|||
include Avatarable
|
||||
include WithUploads
|
||||
|
||||
cache_markdown_field :description
|
||||
cache_markdown_field :description, pipeline: :description
|
||||
|
||||
belongs_to :organization, inverse_of: :organization_detail
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ resources(
|
|||
param: :organization_path,
|
||||
module: :organizations
|
||||
) do
|
||||
collection do
|
||||
post :preview_markdown
|
||||
end
|
||||
|
||||
member do
|
||||
get :groups_and_projects
|
||||
get :users
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Govern
|
||||
group: Authentication
|
||||
description: Third-party authentication providers.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
description: Installation settings.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
stage: SaaS Platforms
|
||||
group: GitLab Dedicated
|
||||
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments"
|
||||
description: 'Learn how to configure your GitLab Dedicated instance.'
|
||||
description: IP allowlists, SAML, maintenance.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Configure GitLab Dedicated **(ULTIMATE)**
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ The subscription is activated.
|
|||
|
||||
You can use one activation code or license key for multiple self-managed instances if the users on
|
||||
these instances are the same or are a subset of your licensed production instance. This means that if
|
||||
you have a licensed production instance of GitLab, and other instances with the same list of users, the
|
||||
you have a licensed production instance of GitLab, and other instances with the same list of users, the
|
||||
production activation code applies, even if these users are configured in different groups and projects.
|
||||
|
||||
### Uploading licenses for scaled architectures
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
description: Backup and restore, move repos, maintenance tasks.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
description: Recommended deployments at scale.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
description: Product settings.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ This page describes endpoints for runners registered to an instance. To create a
|
|||
GET /runners
|
||||
GET /runners/all
|
||||
GET /runners/:id/jobs
|
||||
GET /runners/:id/managers/:system_id/jobs
|
||||
GET /projects/:id/runners
|
||||
GET /groups/:id/runners
|
||||
```
|
||||
|
|
@ -367,7 +368,7 @@ NOTE:
|
|||
The `active` form attribute was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
|
||||
and will be removed in [a future version of the REST API](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
|
||||
|
||||
## List runner's jobs
|
||||
## List jobs processed by a runner
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
|
||||
|
||||
|
|
@ -378,12 +379,13 @@ to projects where the user has at least the Reporter role.
|
|||
GET /runners/:id/jobs
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-----------|---------|----------|---------------------|
|
||||
| `id` | integer | yes | The ID of a runner |
|
||||
| `status` | string | no | Status of the job; one of: `running`, `success`, `failed`, `canceled` |
|
||||
| `order_by`| string | no | Order jobs by `id` |
|
||||
| `sort` | string | no | Sort jobs in `asc` or `desc` order (default: `desc`). Specify `order_by` as well, including for `id`. |
|
||||
| Attribute | Type | Required | Description |
|
||||
|-------------|---------|----------|---------------------|
|
||||
| `id` | integer | yes | The ID of a runner |
|
||||
| `system_id` | string | no | System ID of the machine where the runner manager is running |
|
||||
| `status` | string | no | Status of the job; one of: `running`, `success`, `failed`, `canceled` |
|
||||
| `order_by` | string | no | Order jobs by `id` |
|
||||
| `sort` | string | no | Sort jobs in `asc` or `desc` order (default: `desc`). If `sort` is specified, `order_by` must be specified as well |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/1/jobs?status=running"
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ Each Cell does implement classification endpoint:
|
|||
requests for sharding keys that are not found.
|
||||
- The cached response is for time defined by `expiry` and `refresh`.
|
||||
- The `expiry` defines when the item is removed from cache unless used.
|
||||
- The `refresh` defines when the item needs to be reclassified if used.
|
||||
- The `refresh` defines when the item needs to be reclassified if used.
|
||||
- The refresh is done asynchronously as the request should be served without a delay if they were classified. The refresh is done to ensure that cache is always hot and up-to date.
|
||||
|
||||
For the above example:
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ Each runner has a tag identifier unique to that runner, e.g. `DiscoveryOne`, `tu
|
|||
1. The `preparing` state will wait for a response from the webhook or until timeout.
|
||||
1. The UI should be updated with the current status of the job prerequisites and admission
|
||||
1. For jobs where the webhook times out (1 hour) their status should be set as though the admission was denied with a timeout reasoning. This should
|
||||
be rare in typical circumstances.
|
||||
be rare in typical circumstances.
|
||||
1. Jobs with denied admission can be retried. Retried jobs will be resent to the admission controller without tag mutations or runner filtering reset.
|
||||
1. [`allow_failure`](../../../ci/yaml/index.md#allow_failure) should be updated to support jobs that fail on denied admissions, for example:
|
||||
|
||||
|
|
|
|||
|
|
@ -284,11 +284,11 @@ not an issue per-se.
|
|||
|
||||
New records are created in 2 situations:
|
||||
|
||||
- when the runner calls the `POST /api/v4/runners/verify` endpoint as part of the
|
||||
`gitlab-runner register` command, if the specified runner token is prefixed with `glrt-`.
|
||||
This allows the frontend to determine whether the user has successfully completed the registration and take an
|
||||
appropriate action;
|
||||
- when GitLab is pinged for new jobs and a record matching the `token`+`system_id` does not already exist.
|
||||
- When the runner calls the `POST /api/v4/runners/verify` endpoint as part of the
|
||||
`gitlab-runner register` command, if the specified runner token is prefixed with `glrt-`.
|
||||
This allows the frontend to determine whether the user has successfully completed the registration and take an
|
||||
appropriate action;
|
||||
- When GitLab is pinged for new jobs and a record matching the `token`+`system_id` does not already exist.
|
||||
|
||||
Due to the time-decaying nature of the `ci_runner_machines` records, they are automatically
|
||||
cleaned after 7 days after the last contact from the respective runner.
|
||||
|
|
|
|||
|
|
@ -114,10 +114,10 @@ the data keys mentioned above.
|
|||
### Further investigations required
|
||||
|
||||
1. Management of identities stored in GCP Key Management.
|
||||
We need to investigate how we can correlate and de-multiplex GitLab identities into
|
||||
GCP identities that are used to allow access to cryptographic operations on GCP Key Management.
|
||||
We need to investigate how we can correlate and de-multiplex GitLab identities into
|
||||
GCP identities that are used to allow access to cryptographic operations on GCP Key Management.
|
||||
1. Authentication of clients. Clients to the Secrets Manager could be GitLab Runner or external clients.
|
||||
For each of these, we need a secure and reliable method to authenticate requests to decrypt a secret.
|
||||
For each of these, we need a secure and reliable method to authenticate requests to decrypt a secret.
|
||||
1. Assignment of GCP backed private keys to each identity.
|
||||
|
||||
### Availability on SaaS and Self-Managed
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ The Runner Fleet Dashboard shows:
|
|||
- Number of concurrent jobs executed on most busy runners.
|
||||
- Histogram of job queue times [(available only with ClickHouse)](#enable-more-ci-analytics-features-with-clickhouse).
|
||||
|
||||
Support for usage and cost analysis are proposed in [epic 11183](https://gitlab.com/groups/gitlab-org/-/epics/11183).
|
||||
|
||||

|
||||
|
||||
## View the Runner Fleet Dashboard
|
||||
|
|
@ -32,7 +34,7 @@ To view the runner fleet dashboard:
|
|||
1. Click **Fleet dashboard**.
|
||||
|
||||
Most of the dashboard works without any additional actions, with the
|
||||
exception of **Wait time to pick a job** chart and [proposed features](#whats-next).
|
||||
exception of **Wait time to pick a job** chart and features proposed in [epic 11183](https://gitlab.com/groups/gitlab-org/-/epics/11183).
|
||||
These features require [setting up an additional infrastructure](#enable-more-ci-analytics-features-with-clickhouse).
|
||||
|
||||
## Enable more CI analytics features with ClickHouse **(ULTIMATE EXPERIMENT)**
|
||||
|
|
@ -41,7 +43,7 @@ These features require [setting up an additional infrastructure](#enable-more-ci
|
|||
|
||||
This feature is an [Experiment](../../policy/experiment-beta-support.md).
|
||||
To test it, we have launched an early adopters program.
|
||||
To join the list of users testing this feature, contact us in
|
||||
To join the list of users testing this feature, see
|
||||
[epic 11180](https://gitlab.com/groups/gitlab-org/-/epics/11180).
|
||||
|
||||
### Enable ClickHouse integration and features
|
||||
|
|
@ -53,17 +55,12 @@ To enable additional CI analytics features:
|
|||
|
||||
| Feature flag name | Purpose |
|
||||
|------------------------------------|---------------------------------------------------------------------------|
|
||||
| `ci_data_ingestion_to_click_house` | Enables synchronization of new finished CI builds to Clickhouse database. |
|
||||
| `ci_data_ingestion_to_click_house` | Enables synchronization of new finished CI builds to ClickHouse database. |
|
||||
| `clickhouse_ci_analytics` | Enables the **Wait time to pick a job** chart. |
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For a video walkthrough, see [Setting up Runner Fleet Dashboard with ClickHouse](https://www.youtube.com/watch?v=YpGV95Ctbpk).
|
||||
|
||||
### What's next
|
||||
|
||||
Support for usage and cost analysis are proposed in
|
||||
[epic 11183](https://gitlab.com/groups/gitlab-org/-/epics/11183).
|
||||
|
||||
## Feedback
|
||||
|
||||
To help us improve the Runner Fleet Dashboard, you can provide feedback in
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ Avoid:
|
|||
[_explain why, not what_](https://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/).
|
||||
- Requesting maintainer reviews of merge requests with failed tests. If the tests are failing and you have to request a review, ensure you leave a comment with an explanation.
|
||||
- Excessively mentioning maintainers through email or Slack (if the maintainer is reachable
|
||||
through Slack). If you can't add a reviewer for a merge request, `@` mentioning a maintainer in a comment is acceptable and in all other cases adding a reviewer is sufficient.
|
||||
through Slack). If you can't add a reviewer for a merge request, `@` mentioning a maintainer in a comment is acceptable and in all other cases adding a reviewer is sufficient.
|
||||
|
||||
This saves reviewers time and helps authors catch mistakes earlier.
|
||||
|
||||
|
|
@ -412,7 +412,7 @@ that it meets all requirements, you should:
|
|||
- Select **Approve**.
|
||||
- `@` mention the author to generate a to-do notification, and advise them that their merge request has been reviewed and approved.
|
||||
- Request a review from a maintainer. Default to requests for a maintainer with [domain expertise](#domain-experts),
|
||||
however, if one isn't available or you think the merge request doesn't need a review by a [domain expert](#domain-experts), feel free to follow the [Reviewer roulette](#reviewer-roulette) suggestion.
|
||||
however, if one isn't available or you think the merge request doesn't need a review by a [domain expert](#domain-experts), feel free to follow the [Reviewer roulette](#reviewer-roulette) suggestion.
|
||||
- Remove yourself as a reviewer.
|
||||
|
||||
### The responsibility of the maintainer
|
||||
|
|
@ -580,7 +580,7 @@ experience, refactors the existing code). Then:
|
|||
optionally resolve within the merge request or follow-up at a later stage.
|
||||
- There's a [Chrome/Firefox add-on](https://gitlab.com/conventionalcomments/conventional-comments-button) which you can use to apply [Conventional Comment](https://conventionalcomments.org/) prefixes.
|
||||
- Ensure there are no open dependencies. Check [linked issues](../user/project/issues/related_issues.md) for blockers. Clarify with the authors
|
||||
if necessary. If blocked by one or more open MRs, set an [MR dependency](../user/project/merge_requests/dependencies.md).
|
||||
if necessary. If blocked by one or more open MRs, set an [MR dependency](../user/project/merge_requests/dependencies.md).
|
||||
- After a round of line notes, it can be helpful to post a summary note such as
|
||||
"Looks good to me", or "Just a couple things to address."
|
||||
- Let the author know if changes are required following your review.
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ with additions and improvements.
|
|||
As a merge request (MR) author, you must:
|
||||
|
||||
- Include _Before_ and _After_
|
||||
screenshots (or videos) of your changes in the description, as explained in our
|
||||
[MR workflow](merge_request_workflow.md). These screenshots/videos are very helpful
|
||||
for all reviewers and can speed up the review process, especially if the changes
|
||||
are small.
|
||||
screenshots (or videos) of your changes in the description, as explained in our
|
||||
[MR workflow](merge_request_workflow.md). These screenshots/videos are very helpful
|
||||
for all reviewers and can speed up the review process, especially if the changes
|
||||
are small.
|
||||
- Attach the ~UX label to any merge request that has any user facing changes. This will trigger our
|
||||
Reviewer Roulette to suggest a UX [reviewer](https://about.gitlab.com/handbook/product/ux/product-designer/mr-reviews/#stage-group-mrs).
|
||||
Reviewer Roulette to suggest a UX [reviewer](https://about.gitlab.com/handbook/product/ux/product-designer/mr-reviews/#stage-group-mrs).
|
||||
|
||||
If you are a **team member**: We recommend assigning the Product Designer suggested by the
|
||||
[Reviewer Roulette](../code_review.md#reviewer-roulette) as reviewer. [This helps us](https://about.gitlab.com/handbook/product/ux/product-designer/mr-reviews/#benefits) spread work evenly, improve communication, and make our UI more
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ the consent of one of the technical writers.
|
|||
To add a topic to the global navigation:
|
||||
|
||||
1. In the [`navigation.yaml`](https://gitlab.com/gitlab-org/gitlab-docs/blob/main/content/_data/navigation.yaml)
|
||||
file, add the item.
|
||||
file, add the item.
|
||||
1. Assign the MR to a technical writer for review and merge.
|
||||
|
||||
### Where to add
|
||||
|
|
|
|||
|
|
@ -1140,7 +1140,7 @@ When you take screenshots:
|
|||
Reduce the size of your browser window as much as possible to keep elements close
|
||||
together and reduce empty space. Try to keep the screenshot dimensions as small as possible.
|
||||
- **Review how the image renders on the page.** Preview the image locally or use the
|
||||
review app in the merge request. Make sure the image isn't blurry or overwhelming.
|
||||
review app in the merge request. Make sure the image isn't blurry or overwhelming.
|
||||
- **Be consistent.** Coordinate screenshots with the other screenshots already on
|
||||
a documentation page for a consistent reading experience. Ensure your navigation theme
|
||||
is **Indigo** and the syntax highlighting theme is **Light**. These are the default preferences.
|
||||
|
|
|
|||
|
|
@ -1595,7 +1595,7 @@ Searching is different from [filtering](#filter).
|
|||
When referring to the subscription billing model:
|
||||
|
||||
- For GitLab SaaS, use **seats**. Customers purchase seats. Users occupy seats when they are invited
|
||||
to a group, with some [exceptions](../../../subscriptions/gitlab_com/index.md#how-seat-usage-is-determined).
|
||||
to a group, with some [exceptions](../../../subscriptions/gitlab_com/index.md#how-seat-usage-is-determined).
|
||||
- For GitLab self-managed, use **users**. Customers purchase subscriptions for a specified number of **users**.
|
||||
|
||||
## section
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ The workaround is...
|
|||
If multiple causes or solutions exist, consider putting them into a table format.
|
||||
If you use the exact error message, surround it in backticks so it's styled as code.
|
||||
|
||||
For more guidance on solution types, see [workaround](../../documentation/styleguide/word_list.md#workaround) and [resolution, resolve](../../documentation/styleguide/word_list.md#resolution-resolve).
|
||||
For more guidance on solution types, see [workaround](../../documentation/styleguide/word_list.md#workaround) and [resolution, resolve](../../documentation/styleguide/word_list.md#resolution-resolve).
|
||||
|
||||
## Troubleshooting topic titles
|
||||
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ To guard your licensed feature:
|
|||
```
|
||||
|
||||
1. Optional. If your global feature is also available to namespaces with a paid plan, combine two
|
||||
feature identifiers to allow both administrators and group users. For example:
|
||||
feature identifiers to allow both administrators and group users. For example:
|
||||
|
||||
```ruby
|
||||
License.feature_available?(:my_feature_name) || group.licensed_feature_available?(:my_feature_name_for_namespace) # Both admins and group members can see this EE feature
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ can still have specifics. They are described in their respective
|
|||
The Go upgrade documentation [provides an overview](go_upgrade.md#overview)
|
||||
of how GitLab manages and ships Go binary support.
|
||||
|
||||
If a GitLab component requires a newer version of Go,
|
||||
If a GitLab component requires a newer version of Go,
|
||||
follow the [upgrade process](go_upgrade.md#updating-go-version) to ensure no customer, team, or component is adversely impacted.
|
||||
|
||||
Sometimes, individual projects must also [manage builds with multiple versions of Go](go_upgrade.md#supporting-multiple-go-versions).
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ For example, in German, the word _user_ can be translated into _Benutzer_ (male)
|
|||
|
||||
### Updating the glossary
|
||||
|
||||
To propose additions to the glossary,
|
||||
To propose additions to the glossary,
|
||||
[open an issue](https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&utf8=✓&state=all&label_name[]=Category%3AInternationalization).
|
||||
|
||||
## French translation guidelines
|
||||
|
|
|
|||
|
|
@ -723,7 +723,7 @@ The `with_lock_retries` method **cannot** be used within the `change` method, yo
|
|||
1. For each iteration, set a pre-configured `lock_timeout`.
|
||||
1. Try to execute the given block. (`remove_column`).
|
||||
1. If `LockWaitTimeout` error is raised, sleep for the pre-configured `sleep_time`
|
||||
and retry the block.
|
||||
and retry the block.
|
||||
1. If no error is raised, the current iteration has successfully executed the block.
|
||||
|
||||
For more information check the [`Gitlab::Database::WithLockRetries`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/with_lock_retries.rb) class. The `with_lock_retries` helper method is implemented in the [`Gitlab::Database::MigrationHelpers`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/migration_helpers.rb) module.
|
||||
|
|
@ -733,7 +733,7 @@ In a worst-case scenario, the method:
|
|||
- Executes the block for a maximum of 50 times over 40 minutes.
|
||||
- Most of the time is spent in a pre-configured sleep period after each iteration.
|
||||
- After the 50th retry, the block is executed without `lock_timeout`, just
|
||||
like a standard migration invocation.
|
||||
like a standard migration invocation.
|
||||
- If a lock cannot be acquired, the migration fails with `statement timeout` error.
|
||||
|
||||
The migration might fail if there is a very long running transaction (40+ minutes)
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ To use Docker with `replace` in the `go.mod` file:
|
|||
1. Copy the contents of `command` into the directory of the analyzer. `cp -r /path/to/command path/to/analyzer/command`.
|
||||
1. Add a copy statement in the analyzer's `Dockerfile`: `COPY command /command`.
|
||||
1. Update the `replace` statement to make sure it matches the destination of the `COPY` statement in the step above:
|
||||
`replace gitlab.com/gitlab-org/security-products/analyzers/command/v3 => /command`
|
||||
`replace gitlab.com/gitlab-org/security-products/analyzers/command/v3 => /command`
|
||||
|
||||
## Analyzer scripts
|
||||
|
||||
|
|
@ -189,11 +189,11 @@ are integrated with the existing application, iteration should not be blocked by
|
|||
1. Ensure that the release source (typically the `master` or `main` branch) has a passing pipeline.
|
||||
1. Create a new release for the analyzer project by selecting the **Deployments** menu on the left-hand side of the project window, then selecting the **Releases** sub-menu.
|
||||
1. Select **New release** to open the **New Release** page.
|
||||
1. In the **Tag name** drop down, enter the same version used in the `CHANGELOG.md`, for example `v2.4.2`, and select the option to create the tag (`Create tag v2.4.2` here).
|
||||
1. In the **Release title** text box enter the same version used above, for example `v2.4.2`.
|
||||
1. In the `Release notes` text box, copy and paste the notes from the corresponding version in the `CHANGELOG.md`.
|
||||
1. Leave all other settings as the default values.
|
||||
1. Select **Create release**.
|
||||
1. In the **Tag name** drop down, enter the same version used in the `CHANGELOG.md`, for example `v2.4.2`, and select the option to create the tag (`Create tag v2.4.2` here).
|
||||
1. In the **Release title** text box enter the same version used above, for example `v2.4.2`.
|
||||
1. In the `Release notes` text box, copy and paste the notes from the corresponding version in the `CHANGELOG.md`.
|
||||
1. Leave all other settings as the default values.
|
||||
1. Select **Create release**.
|
||||
|
||||
After following the above process and creating a new release, a new Git tag is created with the `Tag name` provided above. This triggers a new pipeline with the given tag version and a new analyzer Docker image is built.
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ After the above steps have been completed, the automatic release process execute
|
|||
1. After a new version of the analyzer Docker image has been tagged and deployed, test it with the corresponding test project.
|
||||
1. Announce the release on the relevant group Slack channel. Example message:
|
||||
|
||||
> FYI I've just released `ANALYZER_NAME` `ANALYZER_VERSION`. `LINK_TO_RELEASE`
|
||||
> FYI I've just released `ANALYZER_NAME` `ANALYZER_VERSION`. `LINK_TO_RELEASE`
|
||||
|
||||
**Never delete a Git tag that has been pushed** as there is a good
|
||||
chance that the tag will be used and/or cached by the Go package registry.
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ For other regular expressions, here are a few guidelines:
|
|||
|
||||
- If there's a clean non-regex solution, such as `String#start_with?`, consider using it
|
||||
- Ruby supports some advanced regex features like [atomic groups](https://www.regular-expressions.info/atomic.html)
|
||||
and [possessive quantifiers](https://www.regular-expressions.info/possessive.html) that eliminate backtracking
|
||||
and [possessive quantifiers](https://www.regular-expressions.info/possessive.html) that eliminate backtracking
|
||||
- Avoid nested quantifiers if possible (for example `(a+)+`)
|
||||
- Try to be as precise as possible in your regex and avoid the `.` if there's an alternative
|
||||
- For example, Use `_[^_]+_` instead of `_.*_` to match `_text here_`
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ Be sure to include the `feature_flag` tag so that the test can be skipped on the
|
|||
|
||||
- Format: `feature_flag: { name: 'feature_flag_name' }`
|
||||
- Used only for informational purposes at this time. It should be included to help quickly determine what
|
||||
feature flag is under test.
|
||||
feature flag is under test.
|
||||
|
||||
`scope`
|
||||
|
||||
- Format: `feature_flag: { name: 'feature_flag_name', scope: :project }`
|
||||
- When `scope` is set to `:global`, the test will be **skipped on all live .com environments**. This is to avoid issues with feature flag changes affecting other tests or users on that environment.
|
||||
- When `scope` is set to any other value (such as `:project`, `:group` or `:user`), or if no `scope` is specified, the test will only be **skipped on canary, production, and pre-production**.
|
||||
This is due to the fact that administrator access is not available there.
|
||||
This is due to the fact that administrator access is not available there.
|
||||
|
||||
**WARNING:** You are strongly advised to first try and [enable feature flags only for a group, project, user](../../feature_flags/index.md#feature-actors),
|
||||
or [feature group](../../feature_flags/index.md#feature-groups).
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ Adding a delay in API or controller could help reproducing the issue.
|
|||
- [Example 2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101728/diffs): A CSS selector
|
||||
only appears after a GraphQL requests has finished, and the UI has updated.
|
||||
- [Example 3](https://gitlab.com/gitlab-org/gitlab/-/issues/408215): A false-positive test, Capybara immediately returns true after
|
||||
page visit and page is not fully loaded, or if the element is not detectable by webdriver (such as being rendered outside the viewport or behind other elements).
|
||||
page visit and page is not fully loaded, or if the element is not detectable by webdriver (such as being rendered outside the viewport or behind other elements).
|
||||
|
||||
### Datetime-sensitive
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
description: AWS, Google Cloud Platform, Azure.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Install GitLab on a cloud provider.
|
||||
---
|
||||
|
||||
# Installing GitLab on a cloud provider **(FREE SELF)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
description: Linux, Helm, Docker, Operator, source, or scripts.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Read through the GitLab installation methods.
|
||||
---
|
||||
|
||||
# Installation methods **(FREE SELF)**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
description: Prerequisites for installation.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Govern
|
||||
group: Authentication
|
||||
description: SSH key limits, 2FA, tokens, hardening.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -232,13 +232,13 @@ After you dismiss the alert, it doesn't display until another seat is used.
|
|||
The alert displays based on the following seat usage. You cannot configure the
|
||||
amounts at which the alert displays.
|
||||
|
||||
| Seats in subscription | Seat usage |
|
||||
|-----------------------|------------------------------------------------------------------------|
|
||||
| 0-15 | One seat remaining in the subscription. |
|
||||
| 16-25 | Two seats remaining in the subscription. |
|
||||
| 26-99 | 10% of seats have been used. |
|
||||
| 100-999 | 8% of seats have been used. |
|
||||
| 1000+ | 5% of seats have been used |
|
||||
| Seats in subscription | Seat usage |
|
||||
|-----------------------|------------|
|
||||
| 0-15 | One seat remaining in the subscription. |
|
||||
| 16-25 | Two seats remaining in the subscription. |
|
||||
| 26-99 | 10% of seats have been used. |
|
||||
| 100-999 | 8% of seats have been used. |
|
||||
| 1000+ | 5% of seats have been used |
|
||||
|
||||
## Change the linked namespace
|
||||
|
||||
|
|
@ -324,8 +324,8 @@ You can only renew your subscription 15 days before it is due to expire.
|
|||
To renew your subscription:
|
||||
|
||||
1. Sign in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) and beneath your existing subscription, select **Renew**.
|
||||
The **Renew** button displays only 15 days before a subscription expires. If there are more than 15 days before
|
||||
the subscription expires, select **Subscription actions** (**{ellipsis_v}**), then select **Renew subscription** to view the date when you can renew.
|
||||
The **Renew** button displays only 15 days before a subscription expires. If there are more than 15 days before
|
||||
the subscription expires, select **Subscription actions** (**{ellipsis_v}**), then select **Renew subscription** to view the date when you can renew.
|
||||
1. Review your renewal details and complete the payment process.
|
||||
1. Select **Confirm purchase**.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Systems
|
||||
group: Distribution
|
||||
description: Isolated installation.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ Specific information applies to Linux package installations:
|
|||
- PostgreSQL version 14 is the default for fresh installations of GitLab 16.7 and later. However, due to an [issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/7768#note_1652076255), existing Geo secondary sites cannot be upgraded to PostgreSQL version 14. All Geo sites must run the same version of PostgreSQL. If you are adding a new Geo secondary site based on GitLab 16.7 you must take one of the following actions based on your configuration:
|
||||
|
||||
- You are adding your first Geo secondary site: [Upgrade the Primary site to PostgreSQL 14](https://docs.gitlab.com/omnibus/settings/database.html#upgrade-packaged-postgresql-server) before setting up the new Geo secondary site. No special action is required if your primary site is already running PostgreSQL 14.
|
||||
- You are adding a new Geo secondary site to a deployment that already has one or more Geo secondaries:
|
||||
- You are adding a new Geo secondary site to a deployment that already has one or more Geo secondaries:
|
||||
- All sites are running PostgreSQL 13: Install the new Geo secondary site with [pinned PostgreSQL version 13](https://docs.gitlab.com/omnibus/settings/database.html#pin-the-packaged-postgresql-version-fresh-installs-only).
|
||||
- All sites are running PostgreSQL 14: No special action is required.
|
||||
|
||||
|
|
|
|||
|
|
@ -128,10 +128,6 @@ Setting `CS_DEFAULT_BRANCH_IMAGE` avoids duplicate vulnerability findings when a
|
|||
The value of `CS_DEFAULT_BRANCH_IMAGE` indicates the name of the scanned image as it appears on the default branch.
|
||||
For more details on how this deduplication is achieved, see [Setting the default branch image](#setting-the-default-branch-image).
|
||||
|
||||
## Running jobs in merge request pipelines
|
||||
|
||||
See [Use security scanning tools with merge request pipelines](../index.md#use-security-scanning-tools-with-merge-request-pipelines)
|
||||
|
||||
### Customizing the container scanning settings
|
||||
|
||||
There may be cases where you want to customize how GitLab scans your containers. For example, you
|
||||
|
|
@ -243,6 +239,10 @@ if [Dependency Scanning](../dependency_scanning/index.md)
|
|||
is enabled for your project. This happens because GitLab can't automatically deduplicate findings
|
||||
across different types of scanning tools. To understand which types of dependencies are likely to be duplicated, see [Dependency Scanning compared to Container Scanning](../comparison_dependency_and_container_scanning.md).
|
||||
|
||||
#### Running jobs in merge request pipelines
|
||||
|
||||
See [Use security scanning tools with merge request pipelines](../index.md#use-security-scanning-tools-with-merge-request-pipelines).
|
||||
|
||||
#### Available CI/CD variables
|
||||
|
||||
You can [configure](#customizing-the-container-scanning-settings) analyzers by using the following CI/CD variables.
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ The lock file is cached during the build phase and passed to the dependency scan
|
|||
scan occurs. Because the cache is downloaded before the analyzer run occurs, the existence of a lock
|
||||
file in the `CI_BUILDS_DIR` directory triggers the dependency scanning job.
|
||||
|
||||
To prevent this warning, lock files should be committed.
|
||||
To prevent this warning, lock files should be committed.
|
||||
|
||||
## You no longer get the latest Docker image after setting `DS_MAJOR_VERSION` or `DS_ANALYZER_IMAGE`
|
||||
|
||||
|
|
|
|||
|
|
@ -318,10 +318,6 @@ When downloading, you always receive the most recent SAST artifact available.
|
|||
You can enable and configure SAST by using the UI, either with the default settings or with customizations.
|
||||
The method you can use depends on your GitLab license tier.
|
||||
|
||||
### Running jobs in merge request pipelines
|
||||
|
||||
See [Use security scanning tools with merge request pipelines](../index.md#use-security-scanning-tools-with-merge-request-pipelines)
|
||||
|
||||
#### Configure SAST with customizations **(ULTIMATE ALL)**
|
||||
|
||||
> [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/410013) individual SAST analyzers configuration options from the UI in GitLab 16.2.
|
||||
|
|
@ -520,6 +516,10 @@ spotbugs-sast:
|
|||
sast: gl-sast-report.json
|
||||
```
|
||||
|
||||
### Running jobs in merge request pipelines
|
||||
|
||||
See [Use security scanning tools with merge request pipelines](../index.md#use-security-scanning-tools-with-merge-request-pipelines).
|
||||
|
||||
### Available CI/CD variables
|
||||
|
||||
SAST can be configured using the [`variables`](../../../ci/yaml/index.md#variables) parameter in
|
||||
|
|
|
|||
|
|
@ -110,10 +110,6 @@ Secret Detection can detect if a secret was added in one commit and removed in a
|
|||
[merge request pipelines](../../../ci/pipelines/merge_request_pipelines.md). Secret Detection's
|
||||
results are only available after the pipeline is completed.
|
||||
|
||||
## Running jobs in merge request pipelines
|
||||
|
||||
See [Use security scanning tools with merge request pipelines](../index.md#use-security-scanning-tools-with-merge-request-pipelines)
|
||||
|
||||
## Enable Secret Detection
|
||||
|
||||
Prerequisites:
|
||||
|
|
@ -265,6 +261,10 @@ For example:
|
|||
"A personal token for GitLab will look like glpat-JUST20LETTERSANDNUMB" #gitleaks:allow
|
||||
```
|
||||
|
||||
### Running jobs in merge request pipelines
|
||||
|
||||
See [Use security scanning tools with merge request pipelines](../index.md#use-security-scanning-tools-with-merge-request-pipelines).
|
||||
|
||||
### Available CI/CD variables
|
||||
|
||||
Secret Detection can be customized by defining available CI/CD variables:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: "See the Technical Writers assigned to Development Guidelines: https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-development-guidelines"
|
||||
description: "View a list of all the flags available in the GitLab application."
|
||||
description: Complete list of flags.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
layout: 'feature_flags'
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ To view the organizations you have access to:
|
|||
1. On the left sidebar, at the top, select **Create new** (**{plus}**) and **New organization**.
|
||||
1. In the **Organization name** text box, enter a name for the organization.
|
||||
1. In the **Organization URL** text box, enter a path for the organization.
|
||||
1. In the **Organization description** text box, enter a description for the organization. Supports a [limited subset of Markdown](#supported-markdown-for-organization-description).
|
||||
1. In the **Organization avatar** field, select **Upload** or drag and drop an avatar.
|
||||
1. Select **Create organization**.
|
||||
|
||||
## Edit an organization's name
|
||||
|
|
@ -51,6 +53,10 @@ To view the organizations you have access to:
|
|||
1. On the left sidebar, select **Organizations** (**{organization}**) and find the organization you want to edit.
|
||||
1. Select **Settings > General**.
|
||||
1. In the **Organization name** text box, edit the name.
|
||||
1. In the **Organization description** text box, edit the description. Supports a [limited subset of Markdown](#supported-markdown-for-organization-description).
|
||||
1. In the **Organization avatar** field, if an avatar is:
|
||||
- Selected, select **Remove avatar** to remove.
|
||||
- Not selected, select **Upload** or drag and drop an avatar.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Change an organization's URL
|
||||
|
|
@ -72,6 +78,14 @@ To view the organizations you have access to:
|
|||
1. On the left sidebar, select **Organizations** (**{organization}**) and find the organization you want to manage.
|
||||
1. Select **Manage > Users**.
|
||||
|
||||
## Supported Markdown for Organization description
|
||||
|
||||
The Organization description field supports a limited subset of [GitLab Flavored Markdown](../markdown.md), including:
|
||||
|
||||
- [Emphasis](../markdown.md#emphasis)
|
||||
- [Links](../markdown.md#links)
|
||||
- [Superscripts / Subscripts](../markdown.md#superscripts--subscripts)
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Organization developer documentation](../../development/organization/index.md)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
stage: Govern
|
||||
group: Authentication
|
||||
description: Passwords, user moderation, broadcast messages.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ In GitLab 16.3 and later, GitLab is enforcing the cloud licensing requirement fo
|
|||
If you have a GitLab Free subscription and upgrade to GitLab 16.3 or later,
|
||||
to continue having early access to Code Suggestions, you must:
|
||||
|
||||
1. Have a [subscription that supports cloud licensing](https://about.gitlab.com/pricing/).
|
||||
1. Have a [subscription that supports cloud licensing](https://about.gitlab.com/pricing/licensing-faq/cloud-licensing/).
|
||||
1. Make sure you have the latest version of your [IDE extension](index.md#supported-editor-extensions).
|
||||
1. [Manually synchronize your subscription](#manually-synchronize-your-subscription).
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ PATH
|
|||
remote: ../gitlab-safe_request_store
|
||||
specs:
|
||||
gitlab-safe_request_store (0.1.0)
|
||||
rack (~> 2.2.8)
|
||||
request_store
|
||||
|
||||
PATH
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ PATH
|
|||
remote: .
|
||||
specs:
|
||||
gitlab-safe_request_store (0.1.0)
|
||||
rack (~> 2.2.8)
|
||||
request_store
|
||||
|
||||
GEM
|
||||
|
|
@ -35,7 +36,7 @@ GEM
|
|||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
racc (1.6.2)
|
||||
rack (3.0.4.1)
|
||||
rack (2.2.8)
|
||||
rainbow (3.1.1)
|
||||
regexp_parser (2.7.0)
|
||||
request_store (1.5.1)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|||
spec.files = Dir['lib/**/*.rb']
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_runtime_dependency "rack", "~> 2.2.8"
|
||||
spec.add_runtime_dependency "request_store"
|
||||
|
||||
spec.add_development_dependency "gitlab-styles", "~> 10.1.0"
|
||||
|
|
|
|||
|
|
@ -94,6 +94,14 @@ module API
|
|||
|
||||
forbidden!("No access granted") unless can?(current_user, :read_builds, runner)
|
||||
end
|
||||
|
||||
def preload_job_associations(jobs)
|
||||
jobs.preload( # rubocop: disable CodeReuse/ActiveRecord -- this preload is tightly related to the endpoint
|
||||
:user,
|
||||
{ pipeline: { project: [:route, { namespace: :route }] } },
|
||||
{ project: [:route, { namespace: :route }] }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
resource :runners do
|
||||
|
|
@ -217,25 +225,24 @@ module API
|
|||
end
|
||||
params do
|
||||
requires :id, type: Integer, desc: 'The ID of a runner'
|
||||
optional :system_id, type: String, desc: 'System ID associated with the runner manager'
|
||||
optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES
|
||||
optional :order_by, type: String, desc: 'Order by `id`', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
|
||||
optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by `asc` or `desc` order. ' \
|
||||
'Specify `order_by` as well, including for `id`'
|
||||
'Specify `order_by` as well, including for `id`'
|
||||
optional :cursor, type: String, desc: 'Cursor for obtaining the next set of records'
|
||||
use :pagination
|
||||
end
|
||||
get ':id/jobs' do
|
||||
runner = get_runner(params[:id])
|
||||
authenticate_list_runners_jobs!(runner)
|
||||
|
||||
# Optimize query when filtering by runner managers by not asking for count
|
||||
paginator_params = params[:pagination] == :keyset || params[:system_id].blank? ? {} : { without_count: true }
|
||||
|
||||
jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
|
||||
jobs = jobs.preload( # rubocop: disable CodeReuse/ActiveRecord
|
||||
[
|
||||
:user,
|
||||
{ pipeline: { project: [:route, { namespace: :route }] } },
|
||||
{ project: [:route, { namespace: :route }] }
|
||||
]
|
||||
)
|
||||
jobs = paginate(jobs)
|
||||
jobs = preload_job_associations(jobs)
|
||||
jobs = paginate_with_strategies(jobs, paginator_params: paginator_params)
|
||||
jobs.each(&:commit) # batch loads all commits in the page
|
||||
|
||||
present jobs, with: Entities::Ci::JobBasicWithProject
|
||||
|
|
|
|||
|
|
@ -31,13 +31,15 @@ module Gitlab
|
|||
end
|
||||
|
||||
def external_url(name, external_ref)
|
||||
return if GIT_INVALID_URL_REGEX.match?(external_ref)
|
||||
ref = external_ref.to_s
|
||||
|
||||
case external_ref
|
||||
return if GIT_INVALID_URL_REGEX.match?(ref)
|
||||
|
||||
case ref
|
||||
when /\A#{URL_REGEX}\z/o
|
||||
external_ref
|
||||
ref
|
||||
when /\A#{REPO_REGEX}\z/o
|
||||
github_url(external_ref)
|
||||
github_url(ref)
|
||||
else
|
||||
package_url(name)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ module Gitlab
|
|||
result = @app.call(env)
|
||||
|
||||
warden = env['warden']
|
||||
user = catch(:warden) { warden && warden.user } # rubocop:disable Cop/BanCatchThrow -- ignore Warden errors since we're outside Warden::Manager
|
||||
|
||||
unless warden && warden.user
|
||||
unless user
|
||||
# This works because Rack uses these options every time a request is handled, and redis-store
|
||||
# uses the Rack setting first:
|
||||
# 1. https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342
|
||||
|
|
|
|||
|
|
@ -34035,6 +34035,9 @@ msgstr ""
|
|||
msgid "Organization|Organization avatar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Organization description (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Organization name"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -35618,12 +35621,30 @@ msgstr ""
|
|||
msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|Add new"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|An error occurred while fetching downstream pipeline subscriptions."
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|An error occurred while fetching upstream pipeline subscriptions."
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|Delete subscription"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|No project subscribes to the pipelines in this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|Subscribed to this project"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|Subscriptions"
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineSubscriptions|This project is not subscribed to any project pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "PipelineWizardDefaultCommitMessage|Add %{filename}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ module RuboCop
|
|||
#
|
||||
class FileUploads < RuboCop::Cop::Base
|
||||
MSG = 'Do not upload files without workhorse acceleration. ' \
|
||||
'Please refer to https://docs.gitlab.com/ee/development/uploads.html'
|
||||
'Please refer to https://docs.gitlab.com/ee/development/uploads/'
|
||||
|
||||
def_node_matcher :file_in_type, <<~PATTERN
|
||||
(send nil? {:requires :optional}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
require_relative '../support/helpers/test_env'
|
||||
|
||||
# TODO: Remove the debug_with_puts statements below! Used for debugging purposes.
|
||||
# TODO: https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/323#note_1688925316
|
||||
require_relative '../support/helpers/debug_with_puts'
|
||||
|
||||
FactoryBot.define do
|
||||
# Project without repository
|
||||
#
|
||||
|
|
@ -66,6 +70,8 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
after(:build) do |project, evaluator|
|
||||
DebugWithPuts.debug_with_puts "Beginning of after :build of projects factory in spec/factories/projects.rb"
|
||||
|
||||
# Builds and MRs can't have higher visibility level than repository access level.
|
||||
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
|
||||
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
|
||||
|
|
@ -87,6 +93,8 @@ FactoryBot.define do
|
|||
security_and_compliance_access_level: evaluator.security_and_compliance_access_level
|
||||
}
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :build of projects factory in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
project_namespace_hash = {
|
||||
name: evaluator.name,
|
||||
path: evaluator.path,
|
||||
|
|
@ -97,10 +105,16 @@ FactoryBot.define do
|
|||
|
||||
project_namespace_hash[:id] = evaluator.project_namespace_id.presence
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :build of projects factory in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
project.build_project_namespace(project_namespace_hash)
|
||||
project.build_project_feature(project_feature_hash)
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :build of projects factory in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
project.set_runners_token(evaluator.runners_token) if evaluator.runners_token.present?
|
||||
|
||||
DebugWithPuts.debug_with_puts "End of after :build of projects factory in spec/factories/projects.rb"
|
||||
end
|
||||
|
||||
to_create do |project|
|
||||
|
|
@ -108,6 +122,7 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
after(:create) do |project, evaluator|
|
||||
DebugWithPuts.debug_with_puts "Beginning of after :create of projects factory in spec/factories/projects.rb"
|
||||
# Normally the class Projects::CreateService is used for creating
|
||||
# projects, and this class takes care of making sure the owner and current
|
||||
# user have access to the project. Our specs don't use said service class,
|
||||
|
|
@ -116,12 +131,16 @@ FactoryBot.define do
|
|||
project.add_owner(project.first_owner)
|
||||
end
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
if project.group
|
||||
project.run_after_commit_or_now do
|
||||
AuthorizedProjectUpdate::ProjectRecalculateService.new(project).execute
|
||||
end
|
||||
end
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
# assign the delegated `#ci_cd_settings` attributes after create
|
||||
project.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
|
||||
project.merge_pipelines_enabled = evaluator.merge_pipelines_enabled unless evaluator.merge_pipelines_enabled.nil?
|
||||
|
|
@ -133,6 +152,8 @@ FactoryBot.define do
|
|||
project.runner_token_expiration_interval = evaluator.runner_token_expiration_interval unless evaluator.runner_token_expiration_interval.nil?
|
||||
project.runner_token_expiration_interval_human_readable = evaluator.runner_token_expiration_interval_human_readable unless evaluator.runner_token_expiration_interval_human_readable.nil?
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
if evaluator.import_status
|
||||
import_state = project.import_state || project.build_import_state
|
||||
import_state.status = evaluator.import_status
|
||||
|
|
@ -142,8 +163,12 @@ FactoryBot.define do
|
|||
import_state.save!
|
||||
end
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
# simulating ::Projects::ProcessSyncEventsWorker because most tests don't run Sidekiq inline
|
||||
project.create_ci_project_mirror!(namespace_id: project.namespace_id) unless project.ci_project_mirror
|
||||
|
||||
DebugWithPuts.debug_with_puts "End of after :create of projects factory in spec/factories/projects.rb"
|
||||
end
|
||||
|
||||
trait :public do
|
||||
|
|
@ -326,6 +351,7 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
after :create do |project, evaluator|
|
||||
DebugWithPuts.debug_with_puts "Beginning of after :create of trait :repository do in spec/factories/projects.rb"
|
||||
# Specify `lfs: true` to create the LfsObject for the LFS file in the test repo:
|
||||
# https://gitlab.com/gitlab-org/gitlab-test/-/blob/master/files/lfs/lfs_object.iso
|
||||
if evaluator.lfs
|
||||
|
|
@ -351,6 +377,8 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :create of trait :repository do in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
if evaluator.create_templates
|
||||
templates_path = "#{evaluator.create_templates}_templates"
|
||||
|
||||
|
|
@ -380,6 +408,8 @@ FactoryBot.define do
|
|||
branch_name: 'master')
|
||||
end
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :create of trait :repository do in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
if evaluator.create_branch
|
||||
project.repository.create_file(
|
||||
project.creator,
|
||||
|
|
@ -389,6 +419,8 @@ FactoryBot.define do
|
|||
branch_name: evaluator.create_branch)
|
||||
end
|
||||
|
||||
DebugWithPuts.debug_with_puts "During after :create of trait :repository do in spec/factories/projects.rb:#{__LINE__}"
|
||||
|
||||
if evaluator.create_tag
|
||||
project.repository.add_tag(
|
||||
project.creator,
|
||||
|
|
@ -397,6 +429,7 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
project.track_project_repository
|
||||
DebugWithPuts.debug_with_puts "End of after :create of trait :repository do in spec/factories/projects.rb"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,26 +2,28 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::RunnerJobsFinder do
|
||||
let(:project) { create(:project) }
|
||||
let(:runner) { create(:ci_runner, :instance) }
|
||||
let(:user) { create(:user) }
|
||||
RSpec.describe Ci::RunnerJobsFinder, feature_category: :fleet_visibility do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:runner) { create(:ci_runner, :instance) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:runner_manager) { create(:ci_runner_machine, runner: runner) }
|
||||
let_it_be(:jobs) { create_list(:ci_build, 5, runner_manager: runner_manager, project: project) }
|
||||
|
||||
let(:params) { {} }
|
||||
|
||||
subject { described_class.new(runner, user, params).execute }
|
||||
subject(:returned_jobs) { described_class.new(runner, user, params).execute }
|
||||
|
||||
before do
|
||||
before_all do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
context 'when params is empty' do
|
||||
let!(:job) { create(:ci_build, runner: runner, project: project) }
|
||||
let!(:job1) { create(:ci_build, project: project) }
|
||||
|
||||
it 'returns all jobs assigned to Runner' do
|
||||
is_expected.to match_array(job)
|
||||
is_expected.not_to match_array(job1)
|
||||
is_expected.to match_array(jobs)
|
||||
is_expected.not_to include(job1)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -36,35 +38,34 @@ RSpec.describe Ci::RunnerJobsFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the user has permission to read all resources' do
|
||||
let(:user) { create(:user, :admin) }
|
||||
context 'when the user is admin', :enable_admin_mode do
|
||||
let_it_be(:user) { create(:user, :admin) }
|
||||
|
||||
it 'returns all the jobs assigned to a runner' do
|
||||
jobs = create_list(:ci_build, 5, runner: runner, project: project)
|
||||
it { is_expected.to match_array(jobs) }
|
||||
end
|
||||
|
||||
is_expected.to match_array(jobs)
|
||||
context 'when user is developer' do
|
||||
before_all do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(jobs) }
|
||||
end
|
||||
|
||||
context 'when the user has different access levels in different projects' do
|
||||
it 'returns only the jobs the user has permission to see' do
|
||||
guest_project = create(:project)
|
||||
reporter_project = create(:project)
|
||||
let_it_be(:guest_project) { create(:project).tap { |p| p.add_guest(user) } }
|
||||
let_it_be(:guest_jobs) { create_list(:ci_build, 2, runner: runner, project: guest_project) }
|
||||
let_it_be(:reporter_project) { create(:project).tap { |p| p.add_reporter(user) } }
|
||||
let_it_be(:reporter_jobs) { create_list(:ci_build, 3, runner: runner, project: reporter_project) }
|
||||
|
||||
_guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project)
|
||||
reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project)
|
||||
|
||||
guest_project.add_guest(user)
|
||||
reporter_project.add_reporter(user)
|
||||
|
||||
is_expected.to match_array(reporter_jobs)
|
||||
it 'returns only the jobs the user has permission to see', :aggregate_failures do
|
||||
is_expected.to include(*reporter_jobs)
|
||||
is_expected.not_to include(*guest_jobs)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user has reporter access level or greater' do
|
||||
it 'returns jobs assigned to the Runner that the user has accesss to' do
|
||||
jobs = create_list(:ci_build, 3, runner: runner, project: project)
|
||||
|
||||
it 'returns jobs assigned to the Runner that the user has access to' do
|
||||
is_expected.to match_array(jobs)
|
||||
end
|
||||
end
|
||||
|
|
@ -73,24 +74,38 @@ RSpec.describe Ci::RunnerJobsFinder do
|
|||
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
|
||||
context "when status is #{target_status}" do
|
||||
let(:params) { { status: target_status } }
|
||||
let(:exception_status) { (Ci::HasStatus::AVAILABLE_STATUSES - [target_status]).first }
|
||||
let!(:job) { create(:ci_build, runner: runner, project: project, status: target_status) }
|
||||
let!(:other_job) { create(:ci_build, runner: runner, project: project, status: exception_status) }
|
||||
|
||||
before do
|
||||
exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target_status]
|
||||
create(:ci_build, runner: runner, project: project, status: exception_status.first)
|
||||
end
|
||||
|
||||
it 'returns matched job' do
|
||||
is_expected.to eq([job])
|
||||
it 'returns matched job', :aggregate_failures do
|
||||
is_expected.to include(job)
|
||||
is_expected.not_to include(other_job)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when system_id is specified' do
|
||||
let_it_be(:runner_manager2) { create(:ci_runner_machine, runner: runner) }
|
||||
let_it_be(:job2) { create(:ci_build, runner_manager: runner_manager2, project: project) }
|
||||
|
||||
let(:params) { { system_id: runner_manager.system_xid } }
|
||||
|
||||
it 'returns jobs from the specified system' do
|
||||
expect(returned_jobs).to match_array(jobs)
|
||||
end
|
||||
|
||||
context 'when specified system_id does not exist' do
|
||||
let(:params) { { system_id: 'unknown_system' } }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when order_by and sort are specified' do
|
||||
context 'when order_by id and sort is asc' do
|
||||
let(:params) { { order_by: 'id', sort: 'asc' } }
|
||||
let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
|
||||
|
||||
it 'sorts as id: :asc' do
|
||||
is_expected.to eq(jobs.sort_by(&:id))
|
||||
|
|
@ -101,7 +116,6 @@ RSpec.describe Ci::RunnerJobsFinder do
|
|||
context 'when order_by is specified and sort is not specified' do
|
||||
context 'when order_by id and sort is not specified' do
|
||||
let(:params) { { order_by: 'id' } }
|
||||
let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
|
||||
|
||||
it 'sorts as id: :desc' do
|
||||
is_expected.to eq(jobs.sort_by(&:id).reverse)
|
||||
|
|
|
|||
|
|
@ -24,10 +24,14 @@ describe('OrganizationNewApp', () => {
|
|||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const file = new File(['foo'], 'foo.jpg', {
|
||||
type: 'text/plain',
|
||||
});
|
||||
|
||||
const successfulResponseHandler = jest.fn().mockResolvedValue(organizationCreateResponse);
|
||||
|
||||
const createComponent = ({
|
||||
handlers = [
|
||||
[organizationCreateMutation, jest.fn().mockResolvedValue(organizationCreateResponse)],
|
||||
],
|
||||
handlers = [[organizationCreateMutation, successfulResponseHandler]],
|
||||
} = {}) => {
|
||||
mockApollo = createMockApollo(handlers);
|
||||
|
||||
|
|
@ -36,7 +40,12 @@ describe('OrganizationNewApp', () => {
|
|||
|
||||
const findForm = () => wrapper.findComponent(NewEditForm);
|
||||
const submitForm = async () => {
|
||||
findForm().vm.$emit('submit', { name: 'Foo bar', path: 'foo-bar' });
|
||||
findForm().vm.$emit('submit', {
|
||||
name: 'Foo bar',
|
||||
path: 'foo-bar',
|
||||
description: 'Foo bar description',
|
||||
avatar: file,
|
||||
});
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
|
|
@ -74,7 +83,15 @@ describe('OrganizationNewApp', () => {
|
|||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('redirects user to organization web url', () => {
|
||||
it('calls mutation with correct variables and redirects user to organization web url', () => {
|
||||
expect(successfulResponseHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
name: 'Foo bar',
|
||||
path: 'foo-bar',
|
||||
description: 'Foo bar description',
|
||||
avatar: file,
|
||||
},
|
||||
});
|
||||
expect(visitUrlWithAlerts).toHaveBeenCalledWith(
|
||||
organizationCreateResponse.data.organizationCreate.organization.webUrl,
|
||||
[
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
FORM_FIELD_NAME,
|
||||
FORM_FIELD_ID,
|
||||
FORM_FIELD_AVATAR,
|
||||
FORM_FIELD_DESCRIPTION,
|
||||
} from '~/organizations/shared/constants';
|
||||
import organizationUpdateMutation from '~/organizations/settings/general/graphql/mutations/organization_update.mutation.graphql';
|
||||
import {
|
||||
|
|
@ -62,7 +63,13 @@ describe('OrganizationSettings', () => {
|
|||
|
||||
const findForm = () => wrapper.findComponent(NewEditForm);
|
||||
const submitForm = async (data = {}) => {
|
||||
findForm().vm.$emit('submit', { name: 'Foo bar', path: 'foo-bar', avatar: file, ...data });
|
||||
findForm().vm.$emit('submit', {
|
||||
name: 'Foo bar',
|
||||
path: 'foo-bar',
|
||||
description: 'Foo bar description',
|
||||
avatar: file,
|
||||
...data,
|
||||
});
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
|
|
@ -84,7 +91,7 @@ describe('OrganizationSettings', () => {
|
|||
expect(findForm().props()).toMatchObject({
|
||||
loading: false,
|
||||
initialFormValues: defaultProvide.organization,
|
||||
fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_AVATAR],
|
||||
fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_DESCRIPTION, FORM_FIELD_AVATAR],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -117,6 +124,7 @@ describe('OrganizationSettings', () => {
|
|||
input: {
|
||||
id: 'gid://gitlab/Organizations::Organization/1',
|
||||
name: 'Foo bar',
|
||||
description: 'Foo bar description',
|
||||
avatar: file,
|
||||
},
|
||||
});
|
||||
|
|
@ -191,6 +199,7 @@ describe('OrganizationSettings', () => {
|
|||
input: {
|
||||
id: 'gid://gitlab/Organizations::Organization/1',
|
||||
name: 'Foo bar',
|
||||
description: 'Foo bar description',
|
||||
avatar: null,
|
||||
},
|
||||
});
|
||||
|
|
@ -208,6 +217,7 @@ describe('OrganizationSettings', () => {
|
|||
input: {
|
||||
id: 'gid://gitlab/Organizations::Organization/1',
|
||||
name: 'Foo bar',
|
||||
description: 'Foo bar description',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
import NewEditForm from '~/organizations/shared/components/new_edit_form.vue';
|
||||
import OrganizationUrlField from '~/organizations/shared/components/organization_url_field.vue';
|
||||
import AvatarUploadDropzone from '~/vue_shared/components/upload_dropzone/avatar_upload_dropzone.vue';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import {
|
||||
FORM_FIELD_NAME,
|
||||
FORM_FIELD_ID,
|
||||
|
|
@ -18,6 +19,7 @@ describe('NewEditForm', () => {
|
|||
const defaultProvide = {
|
||||
organizationsPath: '/-/organizations',
|
||||
rootUrl: 'http://127.0.0.1:3000/',
|
||||
previewMarkdownPath: '/-/organizations/preview_markdown',
|
||||
};
|
||||
|
||||
const defaultPropsData = {
|
||||
|
|
@ -38,6 +40,7 @@ describe('NewEditForm', () => {
|
|||
const findNameField = () => wrapper.findByLabelText('Organization name');
|
||||
const findIdField = () => wrapper.findByLabelText('Organization ID');
|
||||
const findUrlField = () => wrapper.findComponent(OrganizationUrlField);
|
||||
const findDescriptionField = () => wrapper.findByLabelText('Organization description (optional)');
|
||||
const findAvatarField = () => wrapper.findComponent(AvatarUploadDropzone);
|
||||
|
||||
const setUrlFieldValue = async (value) => {
|
||||
|
|
@ -70,6 +73,30 @@ describe('NewEditForm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('renders `Organization description` field as markdown editor', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findDescriptionField().exists()).toBe(true);
|
||||
expect(wrapper.findComponent(MarkdownField).props()).toMatchObject({
|
||||
markdownPreviewPath: defaultProvide.previewMarkdownPath,
|
||||
markdownDocsPath: helpPagePath('user/organization/index', {
|
||||
anchor: 'organization-description-supported-markdown',
|
||||
}),
|
||||
textareaValue: '',
|
||||
restrictedToolBarItems: [
|
||||
'code',
|
||||
'quote',
|
||||
'bullet-list',
|
||||
'numbered-list',
|
||||
'task-list',
|
||||
'collapsible-section',
|
||||
'table',
|
||||
'attach-file',
|
||||
'full-screen',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `Organization avatar` field is changed', () => {
|
||||
const file = new File(['foo'], 'foo.jpg', {
|
||||
type: 'text/plain',
|
||||
|
|
@ -154,12 +181,13 @@ describe('NewEditForm', () => {
|
|||
|
||||
await findNameField().setValue('Foo bar');
|
||||
await setUrlFieldValue('foo-bar');
|
||||
await findDescriptionField().setValue('Foo bar description');
|
||||
await submitForm();
|
||||
});
|
||||
|
||||
it('emits `submit` event with form values', () => {
|
||||
expect(wrapper.emitted('submit')).toEqual([
|
||||
[{ name: 'Foo bar', path: 'foo-bar', avatar: null }],
|
||||
[{ name: 'Foo bar', path: 'foo-bar', description: 'Foo bar description', avatar: null }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -221,7 +249,7 @@ describe('NewEditForm', () => {
|
|||
});
|
||||
|
||||
it('shows button with loading icon', () => {
|
||||
expect(wrapper.findComponent(GlButton).props('loading')).toBe(true);
|
||||
expect(wrapper.findByTestId('submit-button').props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import App from '~/organizations/show/components/app.vue';
|
||||
import OrganizationAvatar from '~/organizations/show/components/organization_avatar.vue';
|
||||
import OrganizationDescription from '~/organizations/show/components/organization_description.vue';
|
||||
import GroupsAndProjects from '~/organizations/show/components/groups_and_projects.vue';
|
||||
import AssociationCount from '~/organizations/show/components/association_counts.vue';
|
||||
|
||||
|
|
@ -34,6 +35,12 @@ describe('OrganizationShowApp', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('renders organization description and passes organization prop', () => {
|
||||
expect(wrapper.findComponent(OrganizationDescription).props('organization')).toEqual(
|
||||
defaultPropsData.organization,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders groups and projects component and passes `groupsAndProjectsOrganizationPath` prop', () => {
|
||||
expect(
|
||||
wrapper.findComponent(GroupsAndProjects).props('groupsAndProjectsOrganizationPath'),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import OrganizationDescription from '~/organizations/show/components/organization_description.vue';
|
||||
|
||||
describe('OrganizationDescription', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultPropsData = {
|
||||
organization: {
|
||||
id: 1,
|
||||
name: 'GitLab',
|
||||
description_html: '<h1>Foo bar description</h1><script>alert("foo")</script>',
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = ({ propsData = {} } = {}) => {
|
||||
wrapper = mountExtended(OrganizationDescription, {
|
||||
propsData: { ...defaultPropsData, ...propsData },
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
describe('when organization has description', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders description as safe HTML', () => {
|
||||
expect(wrapper.element.innerHTML).toBe('<h1>Foo bar description</h1>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when organization does not have description', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
propsData: { organization: { ...defaultPropsData.organization, description_html: '' } },
|
||||
});
|
||||
});
|
||||
|
||||
it('renders nothing', () => {
|
||||
expect(wrapper.html()).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
||||
let_it_be(:organization) { build_stubbed(:organization) }
|
||||
let_it_be(:organization_detail) { build_stubbed(:organization_detail, description_html: '<em>description</em>') }
|
||||
let_it_be(:organization) { organization_detail.organization }
|
||||
let_it_be(:new_group_path) { '/groups/new' }
|
||||
let_it_be(:new_project_path) { '/projects/new' }
|
||||
let_it_be(:organizations_empty_state_svg_path) { 'illustrations/empty-state/empty-organizations-md.svg' }
|
||||
|
|
@ -11,6 +12,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
let_it_be(:root_url) { 'http://127.0.0.1:3000/' }
|
||||
let_it_be(:groups_empty_state_svg_path) { 'illustrations/empty-state/empty-groups-md.svg' }
|
||||
let_it_be(:projects_empty_state_svg_path) { 'illustrations/empty-state/empty-projects-md.svg' }
|
||||
let_it_be(:preview_markdown_organizations_path) { '/-/organizations/preview_markdown' }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:new_group_path).and_return(new_group_path)
|
||||
|
|
@ -21,6 +23,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
allow(helper).to receive(:root_url).and_return(root_url)
|
||||
allow(helper).to receive(:image_path).with(groups_empty_state_svg_path).and_return(groups_empty_state_svg_path)
|
||||
allow(helper).to receive(:image_path).with(projects_empty_state_svg_path).and_return(projects_empty_state_svg_path)
|
||||
allow(helper).to receive(:preview_markdown_organizations_path).and_return(preview_markdown_organizations_path)
|
||||
end
|
||||
|
||||
describe '#organization_show_app_data' do
|
||||
|
|
@ -41,6 +44,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
'organization' => {
|
||||
'id' => organization.id,
|
||||
'name' => organization.name,
|
||||
'description_html' => organization.description_html,
|
||||
'avatar_url' => 'avatar.jpg'
|
||||
},
|
||||
'groups_and_projects_organization_path' => '/-/organizations/default/groups_and_projects',
|
||||
|
|
@ -91,7 +95,8 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
expect(Gitlab::Json.parse(helper.organization_new_app_data)).to eq(
|
||||
{
|
||||
'organizations_path' => organizations_path,
|
||||
'root_url' => root_url
|
||||
'root_url' => root_url,
|
||||
'preview_markdown_path' => preview_markdown_organizations_path
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
@ -119,10 +124,12 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
|
|||
'id' => organization.id,
|
||||
'name' => organization.name,
|
||||
'path' => organization.path,
|
||||
'description' => organization.description,
|
||||
'avatar' => 'avatar.jpg'
|
||||
},
|
||||
'organizations_path' => organizations_path,
|
||||
'root_url' => root_url
|
||||
'root_url' => root_url,
|
||||
'preview_markdown_path' => preview_markdown_organizations_path
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -100,5 +100,21 @@ RSpec.describe Gitlab::DependencyLinker::PackageJsonLinker do
|
|||
it 'does not link scripts with the same key as a package' do
|
||||
expect(subject).not_to include(link('karma start config/karma.config.js --single-run', 'https://github.com/karma start config/karma.config.js --single-run'))
|
||||
end
|
||||
|
||||
context 'when dependency is not a string' do
|
||||
let(:file_content) do
|
||||
<<-CONTENT.strip_heredoc
|
||||
{
|
||||
"dependencies": {
|
||||
"wrong": {}
|
||||
}
|
||||
}
|
||||
CONTENT
|
||||
end
|
||||
|
||||
it 'does not link it' do
|
||||
expect(subject).not_to include(%(<a href))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -325,14 +325,15 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
|
|||
describe '.with_exposed_artifacts' do
|
||||
subject { described_class.with_exposed_artifacts }
|
||||
|
||||
let!(:job1) { create(:ci_build, pipeline: pipeline) }
|
||||
let!(:job2) { create(:ci_build, options: options, pipeline: pipeline) }
|
||||
let!(:job3) { create(:ci_build, pipeline: pipeline) }
|
||||
let_it_be(:job1) { create(:ci_build, pipeline: pipeline) }
|
||||
let_it_be(:job3) { create(:ci_build, pipeline: pipeline) }
|
||||
|
||||
context 'when some jobs have exposed artifacs and some not' do
|
||||
let!(:job2) { create(:ci_build, options: options, pipeline: pipeline) }
|
||||
|
||||
context 'when some jobs have exposed artifacts and some not' do
|
||||
let(:options) { { artifacts: { expose_as: 'test', paths: ['test'] } } }
|
||||
|
||||
before do
|
||||
before_all do
|
||||
job1.ensure_metadata.update!(has_exposed_artifacts: nil)
|
||||
job3.ensure_metadata.update!(has_exposed_artifacts: false)
|
||||
end
|
||||
|
|
@ -356,10 +357,10 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
|
|||
|
||||
let(:artifact_scope) { Ci::JobArtifact.where(file_type: 'archive') }
|
||||
|
||||
let!(:build_1) { create(:ci_build, :artifacts, pipeline: pipeline) }
|
||||
let!(:build_2) { create(:ci_build, :codequality_reports, pipeline: pipeline) }
|
||||
let!(:build_3) { create(:ci_build, :test_reports, pipeline: pipeline) }
|
||||
let!(:build_4) { create(:ci_build, :artifacts, pipeline: pipeline) }
|
||||
let_it_be(:build_1) { create(:ci_build, :artifacts, pipeline: pipeline) }
|
||||
let_it_be(:build_2) { create(:ci_build, :codequality_reports, pipeline: pipeline) }
|
||||
let_it_be(:build_3) { create(:ci_build, :test_reports, pipeline: pipeline) }
|
||||
let_it_be(:build_4) { create(:ci_build, :artifacts, pipeline: pipeline) }
|
||||
|
||||
it 'returns artifacts matching the given scope' do
|
||||
expect(builds).to contain_exactly(build_1, build_4)
|
||||
|
|
@ -383,10 +384,10 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
|
|||
end
|
||||
|
||||
describe '.with_needs' do
|
||||
let!(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
let!(:build_b) { create(:ci_build, pipeline: pipeline) }
|
||||
let!(:build_need_a) { create(:ci_build_need, build: build) }
|
||||
let!(:build_need_b) { create(:ci_build_need, build: build_b) }
|
||||
let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
let_it_be(:build_b) { create(:ci_build, pipeline: pipeline) }
|
||||
let_it_be(:build_need_a) { create(:ci_build_need, build: build) }
|
||||
let_it_be(:build_need_b) { create(:ci_build_need, build: build_b) }
|
||||
|
||||
context 'when passing build name' do
|
||||
subject { described_class.with_needs(build_need_a.name) }
|
||||
|
|
@ -421,6 +422,33 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
|
|||
end
|
||||
end
|
||||
|
||||
describe '.belonging_to_runner_manager' do
|
||||
subject { described_class.belonging_to_runner_manager(runner_manager) }
|
||||
|
||||
let_it_be(:runner) { create(:ci_runner, :group, groups: [group]) }
|
||||
let_it_be(:build_b) { create(:ci_build, :success) }
|
||||
|
||||
context 'with runner_manager of runner associated with build' do
|
||||
let!(:runner_manager) { create(:ci_runner_machine, runner: runner) }
|
||||
let!(:runner_manager_build) { create(:ci_runner_machine_build, build: build, runner_manager: runner_manager) }
|
||||
|
||||
it { is_expected.to contain_exactly(build) }
|
||||
end
|
||||
|
||||
context 'with runner_manager of runner not associated with build' do
|
||||
let!(:runner_manager) { create(:ci_runner_machine, runner: instance_runner) }
|
||||
let!(:instance_runner) { create(:ci_runner, :with_runner_manager) }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'with nil runner_manager' do
|
||||
let(:runner_manager) { nil }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stick_build_if_status_changed' do
|
||||
it 'sticks the build if the status changed' do
|
||||
job = create(:ci_build, :pending, pipeline: pipeline)
|
||||
|
|
|
|||
|
|
@ -122,23 +122,60 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
|
|||
describe '.for_runner' do
|
||||
subject(:runner_managers) { described_class.for_runner(runner_arg) }
|
||||
|
||||
let_it_be(:runner1) { create(:ci_runner) }
|
||||
let_it_be(:runner_manager11) { create(:ci_runner_machine, runner: runner1) }
|
||||
let_it_be(:runner_manager12) { create(:ci_runner_machine, runner: runner1) }
|
||||
let_it_be(:runner_a) { create(:ci_runner) }
|
||||
let_it_be(:runner_manager_a1) { create(:ci_runner_machine, runner: runner_a) }
|
||||
let_it_be(:runner_manager_a2) { create(:ci_runner_machine, runner: runner_a) }
|
||||
|
||||
context 'with single runner' do
|
||||
let(:runner_arg) { runner1 }
|
||||
let(:runner_arg) { runner_a }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager11, runner_manager12) }
|
||||
it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2) }
|
||||
end
|
||||
|
||||
context 'with multiple runners' do
|
||||
let(:runner_arg) { [runner1, runner2] }
|
||||
let(:runner_arg) { [runner_a, runner_b] }
|
||||
|
||||
let_it_be(:runner2) { create(:ci_runner) }
|
||||
let_it_be(:runner_manager2) { create(:ci_runner_machine, runner: runner2) }
|
||||
let_it_be(:runner_b) { create(:ci_runner) }
|
||||
let_it_be(:runner_manager_b1) { create(:ci_runner_machine, runner: runner_b) }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager11, runner_manager12, runner_manager2) }
|
||||
it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2, runner_manager_b1) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_system_xid' do
|
||||
subject(:runner_managers) { described_class.with_system_xid(system_xid) }
|
||||
|
||||
let_it_be(:runner_a) { create(:ci_runner) }
|
||||
let_it_be(:runner_b) { create(:ci_runner) }
|
||||
let_it_be(:runner_manager_a1) { create(:ci_runner_machine, runner: runner_a, system_xid: 'id1') }
|
||||
let_it_be(:runner_manager_a2) { create(:ci_runner_machine, runner: runner_a, system_xid: 'id2') }
|
||||
let_it_be(:runner_manager_b1) { create(:ci_runner_machine, runner: runner_b, system_xid: 'id1') }
|
||||
|
||||
context 'with single system id' do
|
||||
let(:system_xid) { 'id2' }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager_a2) }
|
||||
end
|
||||
|
||||
context 'with multiple system ids' do
|
||||
let(:system_xid) { %w[id1 id2] }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2, runner_manager_b1) }
|
||||
end
|
||||
|
||||
context 'when chained with another scope' do
|
||||
subject(:runner_managers) { described_class.for_runner(runner).with_system_xid(system_xid) }
|
||||
|
||||
let(:runner) { runner_a }
|
||||
let(:system_xid) { 'id1' }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager_a1) }
|
||||
|
||||
context 'with another runner' do
|
||||
let(:runner) { runner_b }
|
||||
|
||||
it { is_expected.to contain_exactly(runner_manager_b1) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -146,18 +183,18 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
|
|||
let!(:runner_version1) { create(:ci_runner_version, version: '16.0.0', status: :recommended) }
|
||||
let!(:runner_version2) { create(:ci_runner_version, version: '16.0.1', status: :available) }
|
||||
|
||||
let!(:runner1) { create(:ci_runner) }
|
||||
let!(:runner2) { create(:ci_runner) }
|
||||
let!(:runner_manager11) { create(:ci_runner_machine, runner: runner1, version: runner_version1.version) }
|
||||
let!(:runner_manager12) { create(:ci_runner_machine, runner: runner1, version: runner_version2.version) }
|
||||
let!(:runner_manager2) { create(:ci_runner_machine, runner: runner2, version: runner_version2.version) }
|
||||
let!(:runner_a) { create(:ci_runner) }
|
||||
let!(:runner_b) { create(:ci_runner) }
|
||||
let!(:runner_manager_a1) { create(:ci_runner_machine, runner: runner_a, version: runner_version1.version) }
|
||||
let!(:runner_manager_a2) { create(:ci_runner_machine, runner: runner_a, version: runner_version2.version) }
|
||||
let!(:runner_manager_b1) { create(:ci_runner_machine, runner: runner_b, version: runner_version2.version) }
|
||||
|
||||
subject { described_class.aggregate_upgrade_status_by_runner_id }
|
||||
|
||||
it 'contains aggregate runner upgrade status by runner ID' do
|
||||
is_expected.to eq({
|
||||
runner1.id => :recommended,
|
||||
runner2.id => :available
|
||||
runner_a.id => :recommended,
|
||||
runner_b.id => :available
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
@ -189,6 +226,108 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
|
|||
it { is_expected.to eq([runner_manager2, runner_manager1]) }
|
||||
end
|
||||
|
||||
describe '.with_upgrade_status' do
|
||||
subject(:scope) { described_class.with_upgrade_status(upgrade_status) }
|
||||
|
||||
let_it_be(:runner_manager_14_0_0) { create(:ci_runner_machine, version: '14.0.0') }
|
||||
let_it_be(:runner_manager_14_1_0) { create(:ci_runner_machine, version: '14.1.0') }
|
||||
let_it_be(:runner_manager_14_1_1) { create(:ci_runner_machine, version: '14.1.1') }
|
||||
|
||||
before_all do
|
||||
create(:ci_runner_version, version: '14.0.0', status: :available)
|
||||
create(:ci_runner_version, version: '14.1.0', status: :recommended)
|
||||
create(:ci_runner_version, version: '14.1.1', status: :unavailable)
|
||||
end
|
||||
|
||||
context 'as :unavailable' do
|
||||
let(:upgrade_status) { :unavailable }
|
||||
|
||||
it 'returns runners with runner managers whose version is assigned :unavailable' do
|
||||
is_expected.to contain_exactly(runner_manager_14_1_1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as :available' do
|
||||
let(:upgrade_status) { :available }
|
||||
|
||||
it 'returns runners with runner managers whose version is assigned :available' do
|
||||
is_expected.to contain_exactly(runner_manager_14_0_0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as :recommended' do
|
||||
let(:upgrade_status) { :recommended }
|
||||
|
||||
it 'returns runners with runner managers whose version is assigned :recommended' do
|
||||
is_expected.to contain_exactly(runner_manager_14_1_0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_version_prefix' do
|
||||
subject { described_class.with_version_prefix(version_prefix) }
|
||||
|
||||
let_it_be(:runner_manager1) { create(:ci_runner_machine, version: '15.11.0') }
|
||||
let_it_be(:runner_manager2) { create(:ci_runner_machine, version: '15.9.0') }
|
||||
let_it_be(:runner_manager3) { create(:ci_runner_machine, version: '15.11.5') }
|
||||
|
||||
context 'with a prefix string of "15."' do
|
||||
let(:version_prefix) { "15." }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15"' do
|
||||
let(:version_prefix) { "15" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.11."' do
|
||||
let(:version_prefix) { "15.11." }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.11"' do
|
||||
let(:version_prefix) { "15.11" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.9"' do
|
||||
let(:version_prefix) { "15.9" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.11.5"' do
|
||||
let(:version_prefix) { "15.11.5" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a malformed prefix of "V2"' do
|
||||
let(:version_prefix) { "V2" }
|
||||
|
||||
it 'returns no runner managers' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#status', :freeze_time do
|
||||
let(:runner_manager) { build(:ci_runner_machine, created_at: 8.days.ago) }
|
||||
|
||||
|
|
@ -425,106 +564,4 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
|
|||
it { is_expected.to contain_exactly build }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_upgrade_status' do
|
||||
subject(:scope) { described_class.with_upgrade_status(upgrade_status) }
|
||||
|
||||
let_it_be(:runner_manager_14_0_0) { create(:ci_runner_machine, version: '14.0.0') }
|
||||
let_it_be(:runner_manager_14_1_0) { create(:ci_runner_machine, version: '14.1.0') }
|
||||
let_it_be(:runner_manager_14_1_1) { create(:ci_runner_machine, version: '14.1.1') }
|
||||
|
||||
before_all do
|
||||
create(:ci_runner_version, version: '14.0.0', status: :available)
|
||||
create(:ci_runner_version, version: '14.1.0', status: :recommended)
|
||||
create(:ci_runner_version, version: '14.1.1', status: :unavailable)
|
||||
end
|
||||
|
||||
context 'as :unavailable' do
|
||||
let(:upgrade_status) { :unavailable }
|
||||
|
||||
it 'returns runners with runner managers whose version is assigned :unavailable' do
|
||||
is_expected.to contain_exactly(runner_manager_14_1_1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as :available' do
|
||||
let(:upgrade_status) { :available }
|
||||
|
||||
it 'returns runners with runner managers whose version is assigned :available' do
|
||||
is_expected.to contain_exactly(runner_manager_14_0_0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as :recommended' do
|
||||
let(:upgrade_status) { :recommended }
|
||||
|
||||
it 'returns runners with runner managers whose version is assigned :recommended' do
|
||||
is_expected.to contain_exactly(runner_manager_14_1_0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_version_prefix' do
|
||||
subject { described_class.with_version_prefix(version_prefix) }
|
||||
|
||||
let_it_be(:runner_manager1) { create(:ci_runner_machine, version: '15.11.0') }
|
||||
let_it_be(:runner_manager2) { create(:ci_runner_machine, version: '15.9.0') }
|
||||
let_it_be(:runner_manager3) { create(:ci_runner_machine, version: '15.11.5') }
|
||||
|
||||
context 'with a prefix string of "15."' do
|
||||
let(:version_prefix) { "15." }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15"' do
|
||||
let(:version_prefix) { "15" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.11."' do
|
||||
let(:version_prefix) { "15.11." }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.11"' do
|
||||
let(:version_prefix) { "15.11" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager1, runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.9"' do
|
||||
let(:version_prefix) { "15.9" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix string of "15.11.5"' do
|
||||
let(:version_prefix) { "15.11.5" }
|
||||
|
||||
it 'returns runner managers' do
|
||||
is_expected.to contain_exactly(runner_manager3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a malformed prefix of "V2"' do
|
||||
let(:version_prefix) { "V2" }
|
||||
|
||||
it 'returns no runner managers' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ RSpec.describe Organizations::OrganizationDetail, type: :model, feature_category
|
|||
let(:model) { create(:organization_detail) }
|
||||
end
|
||||
|
||||
describe '#description_html' do
|
||||
let_it_be(:model) { create(:organization_detail, description: '### Foo **Bar**') }
|
||||
let(:expected_description) { ' Foo <strong>Bar</strong> ' }
|
||||
|
||||
subject { model.description_html }
|
||||
|
||||
it { is_expected.to eq_no_sourcepos(expected_description) }
|
||||
end
|
||||
|
||||
context 'with uploads' do
|
||||
it_behaves_like 'model with uploads', false do
|
||||
let(:model_object) { create(:organization_detail) }
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
|
||||
describe 'delegations' do
|
||||
it { is_expected.to delegate_method(:description).to(:organization_detail) }
|
||||
it { is_expected.to delegate_method(:description_html).to(:organization_detail) }
|
||||
it { is_expected.to delegate_method(:avatar).to(:organization_detail) }
|
||||
it { is_expected.to delegate_method(:avatar_url).to(:organization_detail) }
|
||||
it { is_expected.to delegate_method(:remove_avatar!).to(:organization_detail) }
|
||||
|
|
|
|||
|
|
@ -836,166 +836,219 @@ RSpec.describe API::Ci::Runners, :aggregate_failures, feature_category: :fleet_v
|
|||
end
|
||||
|
||||
describe 'GET /runners/:id/jobs' do
|
||||
let_it_be(:job_1) { create(:ci_build) }
|
||||
let_it_be(:job_2) { create(:ci_build, :running, runner: shared_runner, project: project) }
|
||||
let_it_be(:job_3) { create(:ci_build, :failed, runner: shared_runner, project: project) }
|
||||
let_it_be(:job_4) { create(:ci_build, :running, runner: project_runner, project: project) }
|
||||
let_it_be(:job_5) { create(:ci_build, :failed, runner: project_runner, project: project) }
|
||||
let(:path) { "/runners/#{project_runner.id}/jobs" }
|
||||
subject(:request) { get api(path, user, **api_params) }
|
||||
|
||||
let_it_be(:shared_runner_manager1) { create(:ci_runner_machine, runner: shared_runner, system_xid: 'id2') }
|
||||
let_it_be(:jobs) do
|
||||
project_runner_manager1 = create(:ci_runner_machine, runner: project_runner, system_xid: 'id1')
|
||||
project_runner_manager2 = create(:ci_runner_machine, runner: two_projects_runner, system_xid: 'id1')
|
||||
|
||||
[
|
||||
create(:ci_build),
|
||||
create(:ci_build, :running, runner_manager: shared_runner_manager1, project: project),
|
||||
create(:ci_build, :failed, runner_manager: shared_runner_manager1, project: project),
|
||||
create(:ci_build, :running, runner_manager: project_runner_manager1, project: project),
|
||||
create(:ci_build, :failed, runner_manager: project_runner_manager1, project: project),
|
||||
create(:ci_build, :running, runner_manager: project_runner_manager2, project: project),
|
||||
create(:ci_build, :running, runner_manager: project_runner_manager2, project: project2)
|
||||
]
|
||||
end
|
||||
|
||||
let(:api_params) { {} }
|
||||
let(:runner_id) { project_runner.id }
|
||||
let(:query_part) { query_params.merge(system_id_params).map { |param| param.join('=') }.join('&') }
|
||||
let(:path) { "/runners/#{runner_id}/jobs?#{query_part}" }
|
||||
let(:query_params) { {} }
|
||||
let(:system_id_params) { {} }
|
||||
|
||||
it_behaves_like 'GET request permissions for admin mode'
|
||||
|
||||
context 'admin user' do
|
||||
let(:user) { admin }
|
||||
let(:api_params) { { admin_mode: true } }
|
||||
|
||||
context 'when runner exists' do
|
||||
context 'when runner is shared' do
|
||||
let(:runner_id) { shared_runner.id }
|
||||
let(:system_id) { 'id2' }
|
||||
|
||||
it 'return jobs' do
|
||||
get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(2)
|
||||
expect(json_response).to match([
|
||||
a_hash_including('id' => jobs[1].id),
|
||||
a_hash_including('id' => jobs[2].id)
|
||||
])
|
||||
end
|
||||
|
||||
it_behaves_like 'an endpoint with keyset pagination', invalid_order: nil do
|
||||
let(:first_record) { jobs[2] }
|
||||
let(:second_record) { jobs[1] }
|
||||
let(:api_call) { api(path, user, **api_params) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner is a project runner' do
|
||||
it 'return jobs' do
|
||||
get api(path, admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(2)
|
||||
expect(json_response).to match([
|
||||
a_hash_including('id' => jobs[3].id),
|
||||
a_hash_including('id' => jobs[4].id)
|
||||
])
|
||||
end
|
||||
|
||||
context 'when user does not have authorization to see all jobs' do
|
||||
it 'shows only jobs it has permission to see' do
|
||||
create(:ci_build, :running, runner: two_projects_runner, project: project)
|
||||
create(:ci_build, :running, runner: two_projects_runner, project: project2)
|
||||
let(:runner_id) { two_projects_runner.id }
|
||||
let(:user) { user2 }
|
||||
let(:api_params) { {} }
|
||||
|
||||
before_all do
|
||||
project.add_guest(user2)
|
||||
project2.add_maintainer(user2)
|
||||
get api("/runners/#{two_projects_runner.id}/jobs", user2)
|
||||
end
|
||||
|
||||
it 'shows only jobs it has permission to see' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response).to match([a_hash_including('id' => jobs[6].id)])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid status is provided' do
|
||||
let(:query_params) { { status: :failed } }
|
||||
|
||||
it 'return filtered jobs' do
|
||||
get api("/runners/#{project_runner.id}/jobs?status=failed", admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first).to include('id' => job_5.id)
|
||||
expect(json_response).to match([a_hash_including('id' => jobs[4].id)])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid order_by is provided' do
|
||||
let(:query_params) { { order_by: :id } }
|
||||
|
||||
context 'when sort order is not specified' do
|
||||
it 'return jobs in descending order' do
|
||||
get api("/runners/#{project_runner.id}/jobs?order_by=id", admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(2)
|
||||
expect(json_response.first).to include('id' => job_5.id)
|
||||
expect(json_response).to match([
|
||||
a_hash_including('id' => jobs[4].id),
|
||||
a_hash_including('id' => jobs[3].id)
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sort order is specified as asc' do
|
||||
let(:query_params) { { order_by: :id, sort: :asc } }
|
||||
|
||||
it 'return jobs sorted in ascending order' do
|
||||
get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(2)
|
||||
expect(json_response.first).to include('id' => job_4.id)
|
||||
expect(json_response).to match([
|
||||
a_hash_including('id' => jobs[3].id),
|
||||
a_hash_including('id' => jobs[4].id)
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid status is provided' do
|
||||
let(:query_params) { { status: 'non-existing' } }
|
||||
|
||||
it 'return 400' do
|
||||
get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid order_by is provided' do
|
||||
let(:query_params) { { order_by: 'non-existing' } }
|
||||
|
||||
it 'return 400' do
|
||||
get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid sort is provided' do
|
||||
let(:query_params) { { sort: 'non-existing' } }
|
||||
|
||||
it 'return 400' do
|
||||
get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'avoids N+1 DB queries' do
|
||||
get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
|
||||
describe 'eager loading' do
|
||||
let(:runner_id) { shared_runner.id }
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new do
|
||||
get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
|
||||
it 'avoids N+1 DB queries' do
|
||||
get api(path, user, **api_params)
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new do
|
||||
get api(path, user, **api_params)
|
||||
end
|
||||
|
||||
create(:ci_build, :failed, runner: shared_runner, project: project)
|
||||
|
||||
expect do
|
||||
get api(path, user, **api_params)
|
||||
end.not_to exceed_query_limit(control.count)
|
||||
end
|
||||
|
||||
create(:ci_build, :failed, runner: shared_runner, project: project)
|
||||
it 'batches loading of commits' do
|
||||
project_with_repo = create(:project, :repository)
|
||||
shared_runner_manager1 = create(:ci_runner_machine, runner: shared_runner, system_xid: 'id1')
|
||||
|
||||
expect do
|
||||
get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
|
||||
end.not_to exceed_query_limit(control)
|
||||
end
|
||||
pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'ddd0f15ae83993f5cb66a927a28673882e99100b')
|
||||
create(:ci_build, :running, runner_manager: shared_runner_manager1, project: project_with_repo, pipeline: pipeline)
|
||||
|
||||
it 'batches loading of commits' do
|
||||
shared_runner = create(:ci_runner, :instance, description: 'Shared runner')
|
||||
pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'c1c67abbaf91f624347bb3ae96eabe3a1b742478')
|
||||
create(:ci_build, :failed, runner_manager: shared_runner_manager1, project: project_with_repo, pipeline: pipeline)
|
||||
|
||||
project_with_repo = create(:project, :repository)
|
||||
pipeline = create(:ci_pipeline, project: project_with_repo, sha: '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
|
||||
create(:ci_build, :failed, runner_manager: shared_runner_manager1, project: project_with_repo, pipeline: pipeline)
|
||||
|
||||
pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'ddd0f15ae83993f5cb66a927a28673882e99100b')
|
||||
create(:ci_build, :running, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
|
||||
expect_next_instance_of(Repository) do |repo|
|
||||
expect(repo).to receive(:commits_by).with(oids:
|
||||
%w[
|
||||
1a0b36b3cdad1d2ee32457c102a8c0b7056fa863
|
||||
c1c67abbaf91f624347bb3ae96eabe3a1b742478
|
||||
]).once.and_call_original
|
||||
end
|
||||
|
||||
pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'c1c67abbaf91f624347bb3ae96eabe3a1b742478')
|
||||
create(:ci_build, :failed, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
|
||||
|
||||
pipeline = create(:ci_pipeline, project: project_with_repo, sha: '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
|
||||
create(:ci_build, :failed, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
|
||||
|
||||
expect_next_instance_of(Repository) do |repo|
|
||||
expect(repo).to receive(:commits_by).with(oids:
|
||||
%w[
|
||||
1a0b36b3cdad1d2ee32457c102a8c0b7056fa863
|
||||
c1c67abbaf91f624347bb3ae96eabe3a1b742478
|
||||
]).once.and_call_original
|
||||
get api(path, admin, admin_mode: true), params: { per_page: 2, order_by: 'id', sort: 'desc' }
|
||||
end
|
||||
|
||||
get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true), params: { per_page: 2, order_by: 'id', sort: 'desc' }
|
||||
end
|
||||
|
||||
context "when runner doesn't exist" do
|
||||
let(:runner_id) { non_existing_record_id }
|
||||
|
||||
it 'returns 404' do
|
||||
get api('/runners/0/jobs', admin, admin_mode: true)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
|
@ -1004,70 +1057,118 @@ RSpec.describe API::Ci::Runners, :aggregate_failures, feature_category: :fleet_v
|
|||
|
||||
context "runner project's administrative user" do
|
||||
context 'when runner exists' do
|
||||
let(:runner_id) { shared_runner.id }
|
||||
|
||||
context 'when runner is shared' do
|
||||
it 'returns 403' do
|
||||
get api("/runners/#{shared_runner.id}/jobs", user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner is a project runner' do
|
||||
let(:runner_id) { project_runner.id }
|
||||
|
||||
it 'return jobs' do
|
||||
get api(path, user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(2)
|
||||
expect(json_response).to match([
|
||||
a_hash_including('id' => jobs[3].id),
|
||||
a_hash_including('id' => jobs[4].id)
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid status is provided' do
|
||||
it 'return filtered jobs' do
|
||||
get api("/runners/#{project_runner.id}/jobs?status=failed", user)
|
||||
context 'when valid status is provided' do
|
||||
let(:query_params) { { status: :failed } }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
it 'return filtered jobs' do
|
||||
request
|
||||
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first).to include('id' => job_5.id)
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to match([
|
||||
a_hash_including('id' => jobs[4].id)
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid status is provided' do
|
||||
it 'return 400' do
|
||||
get api("/runners/#{project_runner.id}/jobs?status=non-existing", user)
|
||||
context 'when invalid status is provided' do
|
||||
let(:query_params) { { status: 'non-existing' } }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
it 'return 400' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when runner doesn't exist" do
|
||||
let(:runner_id) { non_existing_record_id }
|
||||
|
||||
it 'returns 404' do
|
||||
get api('/runners/0/jobs', user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'other authorized user' do
|
||||
it 'does not return jobs' do
|
||||
get api(path, user2)
|
||||
context 'other authorized user' do
|
||||
let(:user) { user2 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
it 'does not return jobs' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized user' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'does not return jobs' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized user' do
|
||||
it 'does not return jobs' do
|
||||
get api(path)
|
||||
context 'with system_id param' do
|
||||
let(:system_id_params) { { system_id: system_id } }
|
||||
let(:system_id) { 'id1' }
|
||||
let(:user) { admin }
|
||||
let(:api_params) { { admin_mode: true } }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
it 'returns jobs from the runner manager' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_limited_pagination_headers
|
||||
expect(response.headers).not_to include('X-Total', 'X-Total-Pages')
|
||||
|
||||
expect(json_response).to match([
|
||||
a_hash_including('id' => jobs[3].id),
|
||||
a_hash_including('id' => jobs[4].id)
|
||||
])
|
||||
end
|
||||
|
||||
context 'when system_id does not match runner' do
|
||||
let(:runner_id) { shared_runner.id }
|
||||
|
||||
it 'does not return jobs' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
expect(json_response).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -119,4 +119,28 @@ RSpec.describe Organizations::OrganizationsController, feature_category: :cell d
|
|||
|
||||
it_behaves_like 'controller action that requires authentication by any user'
|
||||
end
|
||||
|
||||
describe 'POST #preview_markdown' do
|
||||
subject(:gitlab_request) { post preview_markdown_organizations_path, params: { text: '### Foo \n **bar**' } }
|
||||
|
||||
it_behaves_like 'controller action that requires authentication by any user'
|
||||
|
||||
context 'when the user is signed in' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'returns html from markdown' do
|
||||
sign_in(user)
|
||||
gitlab_request
|
||||
|
||||
body = Gitlab::Json.parse(response.body)['body']
|
||||
|
||||
expect(body).not_to include('Foo</h3>')
|
||||
expect(body).to include('<strong>bar</strong>')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue