Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-01-05 18:21:08 +00:00
parent 4ba8ae9707
commit 534ce3b2d0
107 changed files with 1107 additions and 535 deletions

View File

@ -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

View File

@ -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

View File

@ -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: '',

View File

@ -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,

View 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);

View File

@ -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),
},
},

View File

@ -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);

View File

@ -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>

View File

@ -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)"
/>

View File

@ -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 = [

View File

@ -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"

View File

@ -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>

View File

@ -382,6 +382,7 @@ export default {
@click="handleQuote"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('code')"
v-show="!previewMarkdown"
tag="`"
tag-block="```"

View File

@ -27,7 +27,5 @@
// JH-only stylesheets
@import 'application_jh';
/* print styles */
@media print {
@import 'print';
}
// print styles
@import 'print';

View File

@ -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 */

View File

@ -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

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -495,7 +495,7 @@
&::after {
@include gl-dark-invert-keep-hue;
content: image-url('icon_anchor.svg');
content: url('icon_anchor.svg');
visibility: hidden;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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],

View File

@ -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

View File

@ -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)

View File

@ -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
{

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
---

View File

@ -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
---

View File

@ -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)**

View File

@ -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

View File

@ -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
---

View File

@ -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
---

View File

@ -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
---

View File

@ -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"

View File

@ -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:

View File

@ -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:

View File

@ -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.

View File

@ -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

View File

@ -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).
![Runner Fleet Dashboard](img/runner_fleet_dashboard.png)
## 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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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).

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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_`

View File

@ -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).

View File

@ -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

View File

@ -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)**

View File

@ -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)**

View File

@ -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
---

View File

@ -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
---

View File

@ -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**.

View File

@ -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
---

View File

@ -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.

View File

@ -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.

View File

@ -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`

View File

@ -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

View File

@ -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:

View File

@ -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'
---

View File

@ -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)

View File

@ -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
---

View File

@ -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).

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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}

View File

@ -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

View File

@ -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)

View File

@ -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,
[

View File

@ -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',
},
});
});

View File

@ -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);
});
});

View File

@ -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'),

View File

@ -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('');
});
});
});

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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) }

View File

@ -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) }

View File

@ -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

View File

@ -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