Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-09-04 15:07:47 +00:00
parent c01e12a62e
commit f19ea5c8fa
102 changed files with 1399 additions and 703 deletions

View File

@ -399,6 +399,7 @@ jest vue3 predictive:
- jest vue3 mr
- .frontend:rules:jest:predictive
needs:
- !reference [jest vue3 mr, needs]
- "detect-tests"
script:
- if [[ -s "$RSPEC_CHANGED_FILES_PATH" ]] || [[ -s "$RSPEC_MATCHING_JS_FILES_PATH" ]]; then run_timed_command "yarn jest:ci:vue3-mr:predictive-without-fixtures"; fi
@ -408,6 +409,7 @@ jest-with-fixtures vue3 predictive:
- jest-with-fixtures vue3 mr
- .frontend:rules:jest:predictive
needs:
- !reference [jest-with-fixtures vue3 mr, needs]
- "detect-tests"
script:
- if [[ -s "$RSPEC_CHANGED_FILES_PATH" ]] || [[ -s "$RSPEC_MATCHING_JS_FILES_PATH" ]]; then run_timed_command "yarn jest:ci:vue3-mr:predictive-with-fixtures"; fi

View File

@ -1 +1 @@
fb51af45d021201dc8fca3b0aeacc80c78527307
46b9580af93104de9b4c1d3dda81e9aaf7eb4c01

View File

@ -589,7 +589,7 @@ group :test do
# Moved in `test` because https://gitlab.com/gitlab-org/gitlab/-/issues/217527
gem 'derailed_benchmarks', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'gitlab_quality-test_tooling', '~> 1.35.0', require: false, feature_category: :tooling
gem 'gitlab_quality-test_tooling', '~> 1.37.0', require: false, feature_category: :tooling
end
gem 'octokit', '~> 9.0', feature_category: :importers

View File

@ -229,7 +229,7 @@
{"name":"gitlab-styles","version":"12.0.1","platform":"ruby","checksum":"d8a302b0ab0e1f18e2d11501760f1b85c5e70b5e5ca628828a0786c7984ed133"},
{"name":"gitlab_chronic_duration","version":"0.12.0","platform":"ruby","checksum":"0d766944d415b5c831f176871ee8625783fc0c5bfbef2d79a3a616f207ffc16d"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"gitlab_quality-test_tooling","version":"1.35.0","platform":"ruby","checksum":"a1bc432e28cd9d9af6413401c96421f29023d8903be29efbfaec62f369ea244b"},
{"name":"gitlab_quality-test_tooling","version":"1.37.0","platform":"ruby","checksum":"0dcb9de86b8103c00b2a7f1cf6c6e74409707bf1630e7cec3ab7566181dda35b"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},

View File

@ -766,9 +766,10 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
gitlab_quality-test_tooling (1.35.0)
gitlab_quality-test_tooling (1.37.0)
activesupport (>= 7.0, < 7.2)
amatch (~> 0.4.1)
fog-google (~> 1.24, >= 1.24.1)
gitlab (~> 4.19)
http (~> 5.0)
influxdb-client (~> 3.1)
@ -2075,7 +2076,7 @@ DEPENDENCIES
gitlab-utils!
gitlab_chronic_duration (~> 0.12)
gitlab_omniauth-ldap (~> 2.2.0)
gitlab_quality-test_tooling (~> 1.35.0)
gitlab_quality-test_tooling (~> 1.37.0)
gon (~> 6.4.0)
google-apis-androidpublisher_v3 (~> 0.34.0)
google-apis-cloudbilling_v1 (~> 0.21.0)

View File

@ -230,7 +230,7 @@
{"name":"gitlab-styles","version":"12.0.1","platform":"ruby","checksum":"d8a302b0ab0e1f18e2d11501760f1b85c5e70b5e5ca628828a0786c7984ed133"},
{"name":"gitlab_chronic_duration","version":"0.12.0","platform":"ruby","checksum":"0d766944d415b5c831f176871ee8625783fc0c5bfbef2d79a3a616f207ffc16d"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"gitlab_quality-test_tooling","version":"1.35.0","platform":"ruby","checksum":"a1bc432e28cd9d9af6413401c96421f29023d8903be29efbfaec62f369ea244b"},
{"name":"gitlab_quality-test_tooling","version":"1.37.0","platform":"ruby","checksum":"0dcb9de86b8103c00b2a7f1cf6c6e74409707bf1630e7cec3ab7566181dda35b"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},
@ -282,7 +282,7 @@
{"name":"graphiql-rails","version":"1.10.0","platform":"ruby","checksum":"b557f989a737c8b9e985142609bec52fb1e9393a701eb50e02a7c14422891040"},
{"name":"graphlient","version":"0.8.0","platform":"ruby","checksum":"98c408da1d083454e9f5e274f3b0b6261e2a0c2b5f2ed7b3ef9441d46f8e7cb1"},
{"name":"graphlyte","version":"1.0.0","platform":"ruby","checksum":"b5af4ab67dde6e961f00ea1c18f159f73b52ed11395bb4ece297fe628fa1804d"},
{"name":"graphql","version":"2.3.5","platform":"ruby","checksum":"9c367835f86541660d24c3d81632267ecee553d304577aaee070f8ac05860af1"},
{"name":"graphql","version":"2.3.14","platform":"ruby","checksum":"1781f33ab52f250f7bd6082f40ef15363d6acf98009b7acba70d54bee142f295"},
{"name":"graphql-client","version":"0.23.0","platform":"ruby","checksum":"f238b8e451676baad06bd15f95396e018192243dcf12c4e6d13fb41d9a2babc1"},
{"name":"graphql-docs","version":"5.0.0","platform":"ruby","checksum":"76baca6e5a803a4b6a9fbbbfdbf16742b7c4c546c8592b6e1a7aa4e79e562d04"},
{"name":"grpc","version":"1.63.0","platform":"aarch64-linux","checksum":"dc75c5fd570b819470781d9512105dddfdd11d984f38b8e60bb946f92d1f79ee"},

View File

@ -776,9 +776,10 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
gitlab_quality-test_tooling (1.35.0)
gitlab_quality-test_tooling (1.37.0)
activesupport (>= 7.0, < 7.2)
amatch (~> 0.4.1)
fog-google (~> 1.24, >= 1.24.1)
gitlab (~> 4.19)
http (~> 5.0)
influxdb-client (~> 3.1)
@ -915,8 +916,9 @@ GEM
faraday (~> 2.0)
graphql-client
graphlyte (1.0.0)
graphql (2.3.5)
graphql (2.3.14)
base64
fiber-storage
graphql-client (0.23.0)
activesupport (>= 3.0)
graphql (>= 1.13.0)
@ -2102,7 +2104,7 @@ DEPENDENCIES
gitlab-utils!
gitlab_chronic_duration (~> 0.12)
gitlab_omniauth-ldap (~> 2.2.0)
gitlab_quality-test_tooling (~> 1.35.0)
gitlab_quality-test_tooling (~> 1.37.0)
gon (~> 6.4.0)
google-apis-androidpublisher_v3 (~> 0.34.0)
google-apis-cloudbilling_v1 (~> 0.21.0)
@ -2130,7 +2132,7 @@ DEPENDENCIES
graphiql-rails (~> 1.10)
graphlient (~> 0.8.0)
graphlyte (~> 1.0.0)
graphql (~> 2.3.5)
graphql (~> 2.3.14)
graphql-docs (~> 5.0.0)
grpc (= 1.63.0)
gssapi (~> 1.3.1)

View File

@ -186,7 +186,7 @@ export default {
/>
</gl-tab>
<gl-tab lazy>
<gl-tab>
<template #title>
<span>{{ s__('UserMapping|Reassigned') }}</span>
<gl-badge class="gl-tab-counter-badge">{{ reassignedCount || 0 }}</gl-badge>

View File

@ -254,7 +254,7 @@ export default {
id: this.sourceUser.id,
...(hasSelectedUser ? { userId: this.selectedUser.id } : {}),
},
// importSourceUsersQuery used in app.vue
// importSourceUsersQuery used in placeholders_table.vue
refetchQueries: [hasSelectedUser ? {} : importSourceUsersQuery],
})
.then(({ data }) => {

View File

@ -221,7 +221,7 @@ export default {
:label="reassignedUser(item).name"
:sub-label="`@${reassignedUser(item).username}`"
/>
<placeholder-actions v-else :source-user="item" @confirm="onConfirm(item)" />
<placeholder-actions v-else :key="item.id" :source-user="item" @confirm="onConfirm(item)" />
</template>
</gl-table>

View File

@ -403,9 +403,7 @@ export default {
>
</template>
<div v-if="allBranches" class="gl-mt-2" data-testid="all-branches">
{{ $options.i18n.allBranches }}
</div>
<div v-if="allBranches" class="gl-mt-2" data-testid="all-branches">*</div>
<code v-else class="gl-bg-transparent gl-p-0 gl-text-base" data-testid="branch">{{
branch
}}</code>

View File

@ -5,10 +5,11 @@ import { GlDisclosureDropdownGroup, GlLoadingIcon } from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import axios from '~/lib/utils/axios_utils';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import Tracking from '~/tracking';
import Tracking, { InternalEvents } from '~/tracking';
import { logError } from '~/lib/logger';
import { getFormattedItem } from '../utils';
import { EVENT_CLICK_PROJECT_SETTING_IN_COMMAND_PALETTE } from '../tracking_constants';
import {
COMMON_HANDLES,
COMMAND_HANDLE,
@ -27,6 +28,8 @@ import {
import SearchItem from './search_item.vue';
import { commandMapper, linksReducer, autocompleteQuery, fileMapper } from './utils';
const trackingMixin = InternalEvents.mixin();
export default {
name: 'CommandPaletteItems',
components: {
@ -34,7 +37,7 @@ export default {
GlLoadingIcon,
SearchItem,
},
mixins: [Tracking.mixin()],
mixins: [Tracking.mixin(), trackingMixin],
inject: [
'commandPaletteCommands',
'commandPaletteLinks',
@ -261,6 +264,19 @@ export default {
this.loading = false;
}
},
trackingCommands({ text: command }) {
if (!this.isCommandMode || !this.searchContext.project?.id) {
return;
}
const isSettings = this.settings.some((setting) => setting.text === command);
if (!isSettings) {
return;
}
this.trackEvent(EVENT_CLICK_PROJECT_SETTING_IN_COMMAND_PALETTE, {
label: command,
});
},
},
};
</script>
@ -276,6 +292,7 @@ export default {
:group="group"
bordered
:class="{ '!gl-mt-0': index === 0 }"
@action="trackingCommands"
>
<template #list-item="{ item }">
<search-item :item="item" :search-query="searchQuery" />

View File

@ -38,3 +38,6 @@ export const EVENT_CLICK_FREQUENT_GROUP_IN_COMMAND_PALETTE =
'click_frequent_group_in_command_palette';
export const EVENT_CLICK_COMMANDS_SUB_MENU_IN_COMMAND_PALETTE =
'click_commands_sub_menu_in_command_palette';
export const EVENT_CLICK_PROJECT_SETTING_IN_COMMAND_PALETTE =
'click_project_setting_in_command_palette';

View File

@ -33,7 +33,6 @@
@import 'framework/panels';
@import 'framework/popup';
@import 'framework/secondary_navigation_elements';
@import 'framework/selects';
@import 'framework/sidebar';
@import 'framework/super_sidebar';
@import 'framework/brand_logo';

View File

@ -56,7 +56,7 @@ $diff-file-header: 41px;
}
&:hover {
background-color: var(--gl-background-color-strong);
@apply gl-bg-strong;
}
svg {
@ -73,8 +73,7 @@ $diff-file-header: 41px;
}
.diff-content {
background: $white;
color: $gl-text-color;
@apply gl-bg-default gl-text-primary;
.unfold {
cursor: pointer;
@ -82,7 +81,7 @@ $diff-file-header: 41px;
.file-mode-changed {
padding: 10px;
color: $gray-500;
@apply gl-text-subtle;
}
.suppressed-container {
@ -103,7 +102,7 @@ $diff-file-header: 41px;
}
.image {
background: $gray-50;
@apply gl-bg-strong;
text-align: center;
padding: 30px;
@ -113,7 +112,7 @@ $diff-file-header: 41px;
.frame {
display: inline-block;
background-color: $white;
@apply gl-bg-default;
line-height: 0;
img {
@ -304,7 +303,7 @@ $diff-file-header: 41px;
.view-modes {
padding: 10px;
text-align: center;
background: $gray-50;
@apply gl-bg-strong;
ul,
li {
@ -330,7 +329,7 @@ $diff-file-header: 41px;
&.active {
cursor: default;
color: $gl-text-color;
@apply gl-text-primary;
&:hover {
text-decoration: none;
@ -698,8 +697,7 @@ table.code {
}
.note-container {
background-color: $gray-10;
border-top: 1px solid $gray-50;
@apply gl-bg-subtle gl-border-t gl-border-t-subtle;
// double jagged line divider
.discussion-notes + .discussion-notes::before,
@ -709,7 +707,7 @@ table.code {
display: block;
width: 100%;
height: 10px;
background-color: $white;
@apply gl-bg-default;
background-image: linear-gradient(45deg,
transparent,
transparent 73%,
@ -760,7 +758,7 @@ table.code {
.diff-file .note-container > .new-note,
.note-container .discussion-notes.diff-discussions {
margin-left: 100px;
border-left: 1px solid $gray-50;
@apply gl-border-l gl-border-l-subtle;
}
.notes.active {
@ -854,7 +852,7 @@ table.code {
}
&.collapsed {
background-color: $white;
@apply gl-bg-default;
.diff-notes-expand {
display: initial;
@ -871,7 +869,7 @@ table.code {
ul.notes {
li.toggle-replies-widget,
.discussion-reply-holder {
margin-left: 2.5rem;
margin-left: $note-spacing-left;
&[aria-expanded="false"] {
@apply gl-border-b;
@ -892,7 +890,7 @@ table.code {
margin: 0 $gl-padding $gl-padding;
.notes {
border-radius: $gl-border-radius-base;
@apply gl-rounded-base;
}
}

View File

@ -24,10 +24,6 @@
color: $gl-text-color-tertiary;
}
&:not(.ui-sort-disabled):hover {
background: $blue-50;
}
&.unstyled {
&:hover {
background: none;
@ -60,11 +56,6 @@
top: 3px;
}
}
.card.card-body-title {
font-size: $gl-font-size;
line-height: 18px;
}
}
}
@ -73,17 +64,6 @@
ul.bordered-list,
ul.unstyled-list {
@include basic-list;
&.top-list {
li:first-child {
padding-top: 0;
h4,
h5 {
margin-top: 0;
}
}
}
}
ul.unstyled-list > li {
@ -128,15 +108,6 @@ ul.content-list {
.controls {
float: right;
> .control-text {
margin-right: $grid-size;
line-height: $list-text-height;
&:last-child {
margin-right: 0;
}
}
> .btn,
> .btn-group,
> .dropdown.inline {
@ -153,18 +124,6 @@ ul.content-list {
}
}
}
// When dragging a list item
&.ui-sortable-helper {
border-bottom: 0;
}
&.list-placeholder {
background-color: $gray-10;
border: dotted 1px $gray-50;
margin: 1px 0;
min-height: 52px;
}
}
}

View File

@ -14,30 +14,12 @@
pointer-events: none;
}
.div-dropzone-spinner {
position: absolute;
bottom: 10px;
right: 5px;
opacity: 0;
font-size: 20px;
transition: opacity 200ms ease-in-out;
}
.div-dropzone-icon {
display: block;
text-align: center;
font-size: inherit;
}
.div-dropzone-progress {
position: absolute;
top: 7px;
left: -40px;
width: 35px;
font-size: 13px;
text-align: right;
}
.dz-preview {
display: none;
}

View File

@ -235,10 +235,6 @@
.bash {
display: block;
}
&.build-log-rounded {
border-radius: $gl-border-radius-base;
}
}
// Used in EE for Web Terminal

View File

@ -80,10 +80,6 @@
flex-wrap: wrap;
}
}
&.section-align-top {
align-self: flex-start;
}
}
.table-button-footer {

View File

@ -202,11 +202,6 @@
float: none;
}
}
.btn-full {
flex: 1 1 100%;
margin-left: 0;
}
}
}
}

View File

@ -1,56 +0,0 @@
.group-result {
.group-image {
float: left;
}
.group-name {
font-weight: $gl-font-weight-bold;
}
.group-path {
color: $gray-300;
}
}
.project-result {
.project-name {
font-weight: $gl-font-weight-bold;
}
.project-path {
color: $gray-300;
}
}
.user-result {
min-height: 24px;
display: flex;
align-items: center;
.user-image {
float: left;
}
&.no-username {
.user-name {
line-height: 24px;
}
}
}
.approvers-select {
width: calc(70% - #{$gl-spacing-scale-5});
.gl-new-dropdown-toggle {
width: 100%;
}
.dropdown-menu {
width: 100%;
max-width: none;
}
.gl-dropdown-item-check-icon {
display: none;
}
}

View File

@ -200,7 +200,7 @@
}
}
.sidebar-collapsed-icon .sidebar-collapsed-value {
.sidebar-collapsed-icon {
font-size: 12px;
}
@ -277,11 +277,6 @@
.reviewer-grid {
grid-template-areas: 'user approval rerequest remove';
grid-template-columns: 1fr;
&.attention-requests {
grid-template-areas: 'attention user approval remove';
grid-template-columns: auto 1fr;
}
}
.reviewers-dropdown .gl-new-dropdown-panel {
@ -369,10 +364,6 @@
color: inherit;
}
.issuable-header-text {
margin-top: 7px;
}
.gutter-toggle {
display: flex;
align-items: center;
@ -447,10 +438,6 @@
}
}
.block-first {
padding-top: 0;
}
.title {
color: $gl-text-color;
line-height: $gl-line-height-20;
@ -840,29 +827,6 @@
}
}
/*
* Following overrides are done to prevent
* legacy dropdown styles from influencing
* GitLab UI components used within GlDropdown
*/
.right-sidebar-collapsed {
.sidebar-grouped-item {
.sidebar-collapsed-icon {
margin-bottom: 0;
}
.sidebar-collapsed-divider {
line-height: 5px;
font-size: 12px;
color: $gray-500;
+ .sidebar-collapsed-icon {
padding-top: 0;
}
}
}
}
@include media-breakpoint-down(sm) {
// Overriding the following rule with the negative margin
// https://gitlab.com/gitlab-org/gitlab/-/blob/146c43c931c3743a140529307aea616e4aa9ff21/app/assets/stylesheets/framework/sidebar.scss#L1-5

View File

@ -559,11 +559,6 @@ $command-palette-spacing: px-to-rem(14px);
padding: 0.5rem !important;
}
.search-scope-help {
top: 1rem;
right: 3rem;
}
.modal-content {
border-radius: $gl-border-radius-large !important;
}

View File

@ -58,101 +58,6 @@ table {
}
}
}
&.responsive-table {
@include media-breakpoint-down(sm) {
thead {
display: none;
}
&,
tbody,
td {
display: block;
}
td {
color: $gl-text-color-secondary;
}
tbody td.responsive-table-cell {
padding: $gl-padding 0;
width: 100%;
display: flex;
text-align: right;
align-items: center;
justify-content: space-between;
&[data-column]::before {
content: attr(data-column);
display: block;
text-align: left;
padding-right: $gl-padding;
color: $gl-text-color-secondary;
}
&:not([data-column]) {
flex-direction: row-reverse;
}
}
tr.responsive-table-border-start,
tr.responsive-table-border-end {
display: block;
border: solid $gray-100;
padding-left: 0;
padding-right: 0;
> td {
border-color: $gray-100;
&,
&:last-child {
padding-left: $gl-padding;
padding-right: $gl-padding;
}
}
}
tr.responsive-table-border-start {
border-width: 1px 1px 0;
border-radius: $gl-border-radius-base $gl-border-radius-base 0 0;
padding-top: 0;
padding-bottom: 0;
> td:first-child {
border-top: 0; // always have the <table> top border
}
> td:last-child {
border-bottom: 1px solid $gray-100;
}
}
tr.responsive-table-border-end {
border-width: 0 1px 1px;
border-radius: 0 0 $gl-border-radius-base $gl-border-radius-base;
margin-bottom: 2 * $gl-padding;
> :last-child {
border-bottom: 0;
}
}
}
}
}
.responsive-table:not(table) {
@include media-breakpoint-down(sm) {
th {
width: 100%;
}
td {
width: 100%;
float: left;
}
}
}
.top-area + .content-list {

View File

@ -28,9 +28,5 @@
.tab-pane {
padding: $gl-padding;
&.no-padding {
padding: 0;
}
}
}

View File

@ -295,7 +295,8 @@ $search-input-xl-width: 320px;
/*
* Notes
*/
$note-disabled-comment-color: #b2b2b2;
$note-spacing-left: 2.5rem;
$note-spacing-reply-left: 2rem;
/*
* Calendar

View File

@ -29,7 +29,7 @@
.system-note-message {
a {
@apply gl-text-blue-500;
@apply gl-text-link;
}
.gfm-project_member {

View File

@ -21,8 +21,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
@mixin outline-comment() {
margin: $gl-padding $gl-padding 0;
border: 1px solid $border-color;
border-radius: $gl-border-radius-base;
@apply gl-border gl-rounded-base;
}
.issuable-discussion:not(.incident-timeline-events),
@ -33,25 +32,20 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.main-notes-list::before {
background: var(--gray-50, $gray-50);
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark .modal-body & {
background: var(--gray-100, $gray-100);
}
@apply gl-bg-strong;
}
.timeline-entry:not(.draft-note):last-child::before {
background: var(--white);
@apply gl-bg-default;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
background: var(--gray-10);
@apply gl-bg-subtle;
}
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark .modal-body & {
background: var(--gray-50, $gray-50);
@apply gl-bg-strong;
}
}
}
@ -86,7 +80,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-avatar {
border-color: var(--gray-50, $gray-50);
@apply gl-border-subtle;
}
&.note-comment,
@ -96,15 +90,13 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-content {
margin-left: 2.5rem;
border: 1px solid $border-color;
border-radius: $gl-border-radius-base;
margin-left: $note-spacing-left;
@apply gl-border gl-rounded-base;
padding: $gl-padding-4 $gl-padding-8;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong;
border-color: var(--gl-background-color-default);
@apply gl-bg-strong gl-border-default;
}
}
@ -133,26 +125,20 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-content {
margin-left: 2.5rem;
@apply gl-border;
@apply gl-border-b-subtle;
border-top-left-radius: $gl-border-radius-base;
border-top-right-radius: $gl-border-radius-base;
margin-left: $note-spacing-left;
@apply gl-border gl-border-b-subtle gl-rounded-t-base;
padding: $gl-padding-4 $gl-padding-8;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong;
border-bottom-color: var(--gl-background-color-default);
@apply gl-bg-strong gl-border-default;
}
}
}
&:not(:first-of-type) .timeline-entry-inner {
margin-left: 2.5rem;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
@apply gl-bg-subtle;
margin-left: $note-spacing-left;
@apply gl-bg-subtle gl-border-l gl-border-r;
.timeline-content {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 18px;
@ -167,14 +153,12 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-discussion-body {
margin-left: 2rem;
margin-left: $note-spacing-reply-left;
}
}
&:last-of-type .timeline-entry-inner {
border-bottom: 1px solid $border-color;
border-bottom-left-radius: $gl-border-radius-base;
border-bottom-right-radius: $gl-border-radius-base;
@apply gl-border-b gl-rounded-b-base;
}
}
@ -191,21 +175,16 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-entry-inner {
margin-left: 2.5rem;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
@apply gl-bg-subtle;
margin-left: $note-spacing-left;
@apply gl-bg-subtle gl-border-l gl-border-r;
.timeline-content {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
@apply gl-bg-default;
@apply gl-border-b;
@apply gl-border-b-subtle;
@apply gl-bg-default gl-border-b gl-border-b-subtle;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong;
border-bottom-color: var(--gl-background-color-default);
@apply gl-bg-strong gl-border-b-default;
}
}
@ -214,7 +193,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-discussion-body {
margin-left: 2rem;
margin-left: $note-spacing-reply-left;
}
}
}
@ -222,10 +201,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.discussion-reply-holder {
@apply gl-border-1;
@apply gl-border-solid;
@apply gl-border-default;
@apply gl-border-t-0;
@apply gl-border-1 gl-border-solid gl-border-default gl-border-t-0;
@apply gl-bg-subtle;
}
}
@ -240,9 +216,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.notes-content {
border: 0;
@apply gl-border-t-1;
@apply gl-border-solid;
@apply gl-border-default;
@apply gl-border-t;
}
}
@ -385,7 +359,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
// stylelint-disable-next-line gitlab/no-gl-class
a:not(.gl-link) {
color: $blue-600;
@apply gl-text-link;
}
p {
@ -423,13 +397,16 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.system-note-commit-list-toggler {
color: $blue-600;
cursor: pointer;
position: relative;
z-index: 2;
&,
&:hover {
@apply gl-text-link;
}
&:hover {
color: $blue-600;
text-decoration: underline;
}
}
@ -485,10 +462,10 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
cursor: default;
border-top: 0;
border-radius: 0;
margin-left: 2.5rem;
margin-left: $note-spacing-left;
&:hover {
background-color: $gray-10;
@apply gl-bg-subtle;
}
}
@ -497,7 +474,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.diff-content {
margin-left: 2.5rem;
margin-left: $note-spacing-left;
.line_holder td:first-of-type {
@include gl-border-l;
@ -508,7 +485,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.discussion-notes {
margin-left: -2.5rem;
margin-left: -$note-spacing-left;
.notes {
background-color: transparent;
@ -587,8 +564,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong;
border-bottom-color: var(--gl-background-color-default);
@apply gl-bg-strong gl-border-b-default;
}
.toggle-replies-widget {
@ -653,8 +629,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.diff-files-holder {
.discussion-notes .timeline-entry:first-of-type > .timeline-entry-inner {
@apply gl-border-b;
@apply gl-border-b-subtle;
@apply gl-border-b gl-border-b-subtle;
}
}
@ -673,12 +648,12 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.discussion-reply-holder {
border-top: 0;
border-radius: $gl-border-radius-base $gl-border-radius-base;
@apply gl-rounded-t-base;
position: relative;
.discussion-form {
width: 100%;
background-color: $gray-10;
@apply gl-bg-subtle;
padding: 0;
}
@ -691,7 +666,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.code-commit .notes-content,
.diff-viewer > .image ~ .note-container {
background-color: $white;
@apply gl-bg-default;
li.note-comment {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
@ -702,7 +677,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.note-body {
padding: $gl-padding-4 0 $gl-padding-8;
margin-left: 2.5rem;
margin-left: $note-spacing-left;
}
}
}
@ -718,7 +693,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
&:hover,
&.hover {
color: $blue-600;
@apply gl-text-link;
}
&:focus,
@ -732,7 +707,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.author-link {
color: $gl-text-color;
@apply gl-text-primary;
}
// Prevent flickering of link when hovering between `author-name-link` and `.author-username-link`
@ -763,15 +738,12 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.timeline-content {
border-radius: $gl-border-radius-base;
padding: $gl-padding-8 !important;
@include gl-border;
@apply gl-border gl-rounded-base;
// stylelint-disable-next-line gitlab/no-gl-class
.gl-dark & {
@apply gl-bg-strong;
@apply gl-border-b;
border-bottom-color: var(--gl-background-color-default);
@apply gl-bg-strong gl-border-b;
}
&.expanded {
@ -829,13 +801,11 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.note-headline-light,
.discussion-headline-light {
color: $gl-text-color-secondary;
@apply gl-text-subtle;
}
.discussion-headline-light {
a {
color: $blue-600;
}
.discussion-headline-light a {
@apply gl-text-link;
}
.note-headline-meta {
@ -865,7 +835,6 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.discussion-actions {
float: right;
color: $gray-200;
@include media-breakpoint-down(xs) {
width: 100%;
@ -891,7 +860,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
display: inline-flex;
align-items: center;
margin-left: $gl-padding-8;
color: $gray-400;
@apply gl-text-subtle;
@include notes-media('max', map-get($grid-breakpoints, sm) - 1) {
justify-content: flex-start;
@ -912,13 +881,6 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
}
.more-actions-toggle {
&:hover .icon,
&:focus .icon {
color: $blue-600;
}
}
.more-actions-dropdown {
width: 180px;
min-width: 180px;
@ -932,14 +894,14 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
font-size: 13px;
transition: color 0.1s linear;
&:hover {
color: $blue-600;
&:hover,
&:focus {
@apply gl-text-link;
}
&:focus {
text-decoration: underline;
outline: none;
color: $blue-600;
}
}
@ -971,7 +933,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
background: $white;
padding: 1px;
font-size: 12px;
color: $blue-500;
@apply gl-text-link;
border: 1px solid $blue-500;
width: 24px;
height: 24px;
@ -990,7 +952,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
&[disabled] {
background: $white;
border-color: $gray-200;
color: $gray-300;
@apply gl-text-disabled;
cursor: not-allowed;
}
}
@ -1016,14 +978,11 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
}
.disabled-comment {
background-color: $gray-10;
border-radius: $border-radius-base;
border: 1px solid $border-color;
color: $note-disabled-comment-color;
@apply gl-text-disabled gl-bg-subtle gl-border-b gl-rounded-base;
padding: $gl-padding-8 0;
a:not(.learn-more) {
color: $blue-600;
@apply gl-text-link;
}
}
@ -1116,7 +1075,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
&:not(:last-child)::before {
@include vertical-line(16px, 10px);
height: 100%;
background: var(--gray-50, $gray-50);
@apply gl-bg-subtle;
}
}
@ -1142,6 +1101,6 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
// stylelint-disable length-zero-no-unit
@include vertical-line(0px, 15px);
top: auto; // Override top to auto align
background: var(--gray-50, $gray-50);
@apply gl-bg-subtle;
}
}

View File

@ -57,6 +57,7 @@ module ResolvesMergeRequests
participants: MergeRequest.participant_includes,
author: [:author],
merged_at: [:metrics],
closed_at: [:metrics],
commit_count: [:metrics],
diff_stats_summary: [:metrics],
approved_by: [:approved_by_users],

View File

@ -16,6 +16,8 @@ module Types
present_using MergeRequestPresenter
field :closed_at, Types::TimeType, null: true, complexity: 5,
description: 'Timestamp of when the merge request was closed, null if not closed.'
field :created_at, Types::TimeType, null: false,
description: 'Timestamp of when the merge request was created.'
field :description, GraphQL::Types::String, null: true,
@ -325,6 +327,10 @@ module Types
AutoMergeService.new(object.project, current_user).available_strategies(object)
end
def closed_at
object.metrics&.latest_closed_at
end
def commits
object.commits.commits
end

View File

@ -15,6 +15,8 @@ module Ci
.lock('FOR UPDATE SKIP LOCKED')
end
validates :project_id, presence: true, on: :create
def self.bulk_import(artifacts, pick_up_at = nil)
attributes = artifacts.each.with_object([]) do |artifact, accumulator|
record = artifact.to_deleted_object_attrs(pick_up_at)

View File

@ -215,7 +215,8 @@ module Ci
file_store: file_store,
store_dir: final_path_store_dir || file.store_dir.to_s,
file: final_path_filename || file_identifier,
pick_up_at: pick_up_at || expire_at || Time.current
pick_up_at: pick_up_at || expire_at || Time.current,
project_id: project_id
}
end

View File

@ -49,7 +49,8 @@ module Enums
ondemand_dast_scan: 13,
ondemand_dast_validation: 14,
security_orchestration_policy: 15,
container_registry_push: 16
container_registry_push: 16,
duo_workflow: 17
}
end
@ -65,7 +66,7 @@ module Enums
# - when a container_registry_push pipeline runs it is for security testing purpose and should
# not affect the ref CI status.
def self.dangling_sources
sources.slice(:webide, :parent_pipeline, :ondemand_dast_scan, :ondemand_dast_validation, :security_orchestration_policy, :container_registry_push)
sources.slice(:webide, :parent_pipeline, :ondemand_dast_scan, :ondemand_dast_validation, :security_orchestration_policy, :container_registry_push, :duo_workflow)
end
# CI sources are those pipeline events that affect the CI status of the ref

View File

@ -36,6 +36,12 @@ module VirtualRegistries
fuzzy_search(query, [:relative_path], use_minimum_char_limit: false)
end
def filename
return unless relative_path
File.basename(relative_path)
end
private
def set_object_storage_key

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module VirtualRegistries
module Packages
module Maven
class UpstreamPolicy < ::BasePolicy
delegate { @subject.registry }
end
end
end
end

View File

@ -16,15 +16,7 @@ module Ci
private
def running_timed_out_builds
if Feature.enabled?(:ci_new_query_for_running_stuck_jobs)
Ci::Build
.running
.created_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago)
.updated_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago)
.order(created_at: :asc, project_id: :asc) # rubocop:disable CodeReuse/ActiveRecord
else
Ci::Build.running.updated_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago)
end
Ci::Build.running.updated_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago)
end
end
end

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
module VirtualRegistries
module Packages
module Maven
module CachedResponses
class CreateService < ::BaseContainerService
alias_method :upstream, :container
ERRORS = {
unauthorized: ServiceResponse.error(message: 'Unauthorized', reason: :unauthorized),
path_not_present: ServiceResponse.error(message: 'Parameter path not present', reason: :path_not_present),
file_not_present: ServiceResponse.error(message: 'Parameter file not present', reason: :file_not_present)
}.freeze
def initialize(upstream:, current_user: nil, params: {})
super(container: upstream, current_user: current_user, params: params)
end
def execute
return ERRORS[:path_not_present] unless path.present?
return ERRORS[:file_not_present] unless file.present?
return ERRORS[:unauthorized] unless allowed?
now = Time.zone.now
# the uploader's filename function depends on the relative_path.
# The relative_path needs to be set before the file value is assigned.
cr = upstream.cached_responses.build(
group_id: upstream.group_id,
upstream_etag: etag,
upstream_checked_at: now,
size: file.size,
relative_path: relative_path,
downloaded_at: now
)
cr.update!(file: file)
ServiceResponse.success(payload: { cached_response: cr })
end
private
def allowed?
can?(current_user, :read_virtual_registry, upstream)
end
def file
params[:file]
end
def path
params[:path]
end
def relative_path
"/#{path}"
end
def etag
params[:etag]
end
end
end
end
end
end

View File

@ -8,38 +8,47 @@ module VirtualRegistries
TIMEOUT = 5
ERRORS = {
path_not_present: ServiceResponse.error(message: 'Path not present', reason: :path_not_present),
unauthorized: ServiceResponse.error(message: 'Unauthorized', reason: :unauthorized),
no_upstreams: ServiceResponse.error(message: 'No upstreams set', reason: :no_upstreams),
file_not_found_on_upstreams: ServiceResponse.error(
message: 'File not found on any upstream',
reason: :file_not_found_on_upstreams
),
upstream_not_available: ServiceResponse.error(
message: 'Upstream not available',
reason: :upstream_not_available
)
}.freeze
def initialize(registry:, current_user: nil, params: {})
super(container: registry, current_user: current_user, params: params)
end
def execute
return ServiceResponse.error(message: 'Path not present', reason: :path_not_present) unless path.present?
return ServiceResponse.error(message: 'Unauthorized', reason: :unauthorized) unless allowed?
unless registry.upstream.present?
return ServiceResponse.error(message: 'No upstreams set', reason: :no_upstreams)
end
return ERRORS[:path_not_present] unless path.present?
return ERRORS[:unauthorized] unless allowed?
return ERRORS[:no_upstreams] unless registry.upstream.present?
# TODO check cached responses here
# If one exists and can be used, return it.
# https://gitlab.com/gitlab-org/gitlab/-/issues/467983
handle_upstream(registry.upstream)
check_upstream(registry.upstream)
end
private
def handle_upstream(upstream)
def check_upstream(upstream)
url = upstream.url_for(path)
headers = upstream.headers
response = head_upstream(url: url, headers: headers)
if response.success?
workhorse_send_url_response(url: url, headers: headers)
else
ServiceResponse.error(message: 'File not found on any upstream', reason: :file_not_found_on_upstreams)
end
return ERRORS[:file_not_found_on_upstreams] unless response.success?
workhorse_upload_url_response(url: url, upstream: upstream)
rescue *::Gitlab::HTTP::HTTP_ERRORS
ServiceResponse.error(message: 'Upstream not available', reason: :upstream_not_available)
ERRORS[:upstream_not_available]
end
def head_upstream(url:, headers:)
@ -54,9 +63,12 @@ module VirtualRegistries
params[:path]
end
def workhorse_send_url_response(url:, headers:)
def workhorse_upload_url_response(url:, upstream:)
ServiceResponse.success(
payload: { action: :workhorse_send_url, action_params: { url: url, headers: headers } }
payload: {
action: :workhorse_upload_url,
action_params: { url: url, upstream: upstream }
}
)
end
end

View File

@ -10,6 +10,8 @@ module VirtualRegistries
before :cache, :set_content_type
delegate :filename, to: :model
def store_dir
dynamic_segment
end

View File

@ -0,0 +1,21 @@
---
description: User clicks a project setting in the command palette
internal_events: true
action: click_project_setting_in_command_palette
identifiers:
- project
- namespace
- user
additional_properties:
label:
description: text of the result that was selected
product_group: personal_productivity
milestone: '17.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163735
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -1,8 +0,0 @@
---
name: blame_page_pagination
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85827
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360927
milestone: '15.0'
type: development
group: group::source code
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: ci_new_query_for_running_stuck_jobs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71013
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339264
milestone: '14.4'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -0,0 +1,22 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_project_setting_in_command_palette_monthly
description: Monthly count of unique users who clicked a project setting in the command palette
product_group: personal_productivity
performance_indicator_type: []
value_type: number
status: active
milestone: '17.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163735
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_project_setting_in_command_palette
unique: user.id

View File

@ -0,0 +1,22 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_project_setting_in_command_palette_weekly
description: Weekly count of unique users who clicked a project setting in the command palette
product_group: personal_productivity
performance_indicator_type: []
value_type: number
status: active
milestone: '17.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163735
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_project_setting_in_command_palette
unique: user.id

View File

@ -0,0 +1,10 @@
---
migration_job_name: SetProjectVulnerabilityCount
description: Sets the number of vulnerabilities a project has in the vulnerbaility_count column in the project_statistics table
feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163151
milestone: '17.4'
queued_migration_version: 20240818090801
# Replace with the approximate date you think it's best to ensure the completion of this BBM.
finalize_after: "2024-09-01"
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class CapWorkspacesMaxTerminationToOneYear < Gitlab::Database::Migration[2.2]
restrict_gitlab_migration gitlab_schema: :gitlab_main
milestone '17.4'
# NOTE: see the following issue for the reasoning behind this value being the hard maximum termination limit:
# https://gitlab.com/gitlab-org/gitlab/-/issues/471994
TERMINATION_LIMIT_IN_HOURS = 8760
def up
execute(<<~SQL)
UPDATE workspaces
SET max_hours_before_termination = #{TERMINATION_LIMIT_IN_HOURS}
WHERE max_hours_before_termination > #{TERMINATION_LIMIT_IN_HOURS}
SQL
end
def down
# no-op
end
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
class AddWorkspaceSudoAccessToWorkspaceAgentConfigs < Gitlab::Database::Migration[2.2]
milestone '17.4'
disable_ddl_transaction!
def up
with_lock_retries do
add_column :workspaces_agent_configs, :allow_privilege_escalation, :boolean,
default: false, null: false, if_not_exists: true
add_column :workspaces_agent_configs, :use_kubernetes_user_namespaces, :boolean,
default: false, null: false, if_not_exists: true
add_column :workspaces_agent_configs, :default_runtime_class, :text, default: "", null: false, if_not_exists: true
add_column :workspaces_agent_configs, :annotations, :jsonb, default: {}, null: false, if_not_exists: true
add_column :workspaces_agent_configs, :labels, :jsonb, default: {}, null: false, if_not_exists: true
end
# Kubernetes runtime class names are limited to 253 characters
add_text_limit :workspaces_agent_configs, :default_runtime_class, 253
end
def down
with_lock_retries do
remove_column :workspaces_agent_configs, :allow_privilege_escalation, if_exists: true
remove_column :workspaces_agent_configs, :use_kubernetes_user_namespaces, if_exists: true
remove_column :workspaces_agent_configs, :default_runtime_class, if_exists: true
remove_column :workspaces_agent_configs, :annotations, if_exists: true
remove_column :workspaces_agent_configs, :labels, if_exists: true
end
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddProjectIdToCiDeletedObject < Gitlab::Database::Migration[2.2]
milestone '17.4'
def change
add_column(:ci_deleted_objects, :project_id, :bigint)
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddUniqueIndexOnAddOnPurchasesOnAddOnAndNamespace < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.4'
INDEX_NAME = 'index_add_on_purchases_on_add_on_id_and_namespace_id_not_null'
def up
add_concurrent_index :subscription_add_on_purchases,
[:subscription_add_on_id, :namespace_id],
name: INDEX_NAME,
unique: true,
where: 'namespace_id IS NOT NULL'
end
def down
remove_concurrent_index_by_name :subscription_add_on_purchases, name: INDEX_NAME
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddUniqueIndexOnAddOnPurchasesOnAddOn < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.4'
INDEX_NAME = 'index_add_on_purchases_on_add_on_id_and_namespace_id_null'
def up
add_concurrent_index :subscription_add_on_purchases,
[:subscription_add_on_id],
name: INDEX_NAME,
unique: true,
where: 'namespace_id IS NULL'
end
def down
remove_concurrent_index_by_name :subscription_add_on_purchases, name: INDEX_NAME
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
class QueueSetProjectVulnerabilityCount < Gitlab::Database::Migration[2.2]
milestone '17.4'
# Select the applicable gitlab schema for your batched background migration
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "SetProjectVulnerabilityCount"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:project_settings,
:project_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :project_settings, :project_id, [])
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class IndexCiDeletedObjectOnProjectId < Gitlab::Database::Migration[2.2]
milestone '17.4'
disable_ddl_transaction!
TABLE_NAME = :ci_deleted_objects
INDEX_NAME = :index_ci_deleted_objects_on_project_id
def up
add_concurrent_index(TABLE_NAME, :project_id, name: INDEX_NAME)
end
def down
remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddNotNullToCiDeletedProjectId < Gitlab::Database::Migration[2.2]
milestone '17.4'
disable_ddl_transaction!
TABLE_NAME = :ci_deleted_objects
COLUMN_NAME = :project_id
def up
add_not_null_constraint(TABLE_NAME, COLUMN_NAME, validate: false)
end
def down
remove_not_null_constraint(TABLE_NAME, COLUMN_NAME)
end
end

View File

@ -0,0 +1 @@
a5ea1dd3f424f12824e787b108a476f63d692f84baf1e432751c9a0b5e63b3df

View File

@ -0,0 +1 @@
deb023ca7e2268ad69d5e88c6ac6d9a54ef0691cd6e4dbce2ec529e5b5c27b28

View File

@ -0,0 +1 @@
19119a07f637d49eb954d67bf80543244a6330573e1e8e450c1cdb1ebd56f523

View File

@ -0,0 +1 @@
2b5c7923653143810c7f5e88a62d08a0dab1f335b5bee49e36378990a0446f13

View File

@ -0,0 +1 @@
fc71d9f85d2709dda6645527c221700304a8feebeb40cc89b9c836fabd1ba00b

View File

@ -0,0 +1 @@
13407f6d933ff98a4996baac347f2ecb1794ead71a3d002a9a57f247ac0290a8

View File

@ -0,0 +1 @@
0fa026a081dfe21b0e37ace16fcd77621cab43445240892c8a11ebc918a0475c

View File

@ -0,0 +1 @@
cddbd6bf25fdc9e6e82fe1db2f6434c4b21635fa3c60559e71c6e6ad357a82e5

View File

@ -7946,6 +7946,7 @@ CREATE TABLE ci_deleted_objects (
pick_up_at timestamp with time zone DEFAULT now() NOT NULL,
store_dir text NOT NULL,
file text NOT NULL,
project_id bigint,
CONSTRAINT check_5e151d6912 CHECK ((char_length(store_dir) <= 1024))
);
@ -20564,7 +20565,13 @@ CREATE TABLE workspaces_agent_configs (
network_policy_egress jsonb DEFAULT '[{"allow": "0.0.0.0/0", "except": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]}]'::jsonb NOT NULL,
default_resources_per_workspace_container jsonb DEFAULT '{}'::jsonb NOT NULL,
max_resources_per_workspace jsonb DEFAULT '{}'::jsonb NOT NULL,
allow_privilege_escalation boolean DEFAULT false NOT NULL,
use_kubernetes_user_namespaces boolean DEFAULT false NOT NULL,
default_runtime_class text DEFAULT ''::text NOT NULL,
annotations jsonb DEFAULT '{}'::jsonb NOT NULL,
labels jsonb DEFAULT '{}'::jsonb NOT NULL,
CONSTRAINT check_58759a890a CHECK ((char_length(dns_zone) <= 256)),
CONSTRAINT check_720388a28c CHECK ((char_length(default_runtime_class) <= 253)),
CONSTRAINT check_dca877fba1 CHECK ((default_max_hours_before_termination <= 8760)),
CONSTRAINT check_eab6e375ad CHECK ((max_hours_before_termination_limit <= 8760)),
CONSTRAINT check_ee2464835c CHECK ((char_length(gitlab_workspaces_proxy_namespace) <= 63))
@ -23330,6 +23337,9 @@ ALTER TABLE ci_job_variables
ALTER TABLE ci_runners
ADD CONSTRAINT check_91230910ec CHECK ((char_length((name)::text) <= 256)) NOT VALID;
ALTER TABLE ci_deleted_objects
ADD CONSTRAINT check_98f90d6c53 CHECK ((project_id IS NOT NULL)) NOT VALID;
ALTER TABLE sprints
ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID;
@ -26887,6 +26897,10 @@ CREATE UNIQUE INDEX index_activity_pub_releases_sub_on_project_id_inbox_url ON a
CREATE UNIQUE INDEX index_activity_pub_releases_sub_on_project_id_sub_url ON activity_pub_releases_subscriptions USING btree (project_id, lower(subscriber_url));
CREATE UNIQUE INDEX index_add_on_purchases_on_add_on_id_and_namespace_id_not_null ON subscription_add_on_purchases USING btree (subscription_add_on_id, namespace_id) WHERE (namespace_id IS NOT NULL);
CREATE UNIQUE INDEX index_add_on_purchases_on_add_on_id_and_namespace_id_null ON subscription_add_on_purchases USING btree (subscription_add_on_id) WHERE (namespace_id IS NULL);
CREATE INDEX index_add_on_purchases_on_organization_id ON subscription_add_on_purchases USING btree (organization_id);
CREATE INDEX index_agent_activity_events_on_agent_id_and_recorded_at_and_id ON agent_activity_events USING btree (agent_id, recorded_at, id);
@ -27375,6 +27389,8 @@ CREATE INDEX index_ci_daily_build_group_report_results_on_project_and_date ON ci
CREATE INDEX index_ci_deleted_objects_on_pick_up_at ON ci_deleted_objects USING btree (pick_up_at);
CREATE INDEX index_ci_deleted_objects_on_project_id ON ci_deleted_objects USING btree (project_id);
CREATE INDEX index_ci_finished_build_ch_sync_events_for_partitioned_query ON ONLY p_ci_finished_build_ch_sync_events USING btree (((build_id % (100)::bigint)), build_id) WHERE (processed = false);
CREATE INDEX index_ci_finished_pipeline_ch_sync_events_for_partitioned_query ON ONLY p_ci_finished_pipeline_ch_sync_events USING btree (((pipeline_id % (100)::bigint)), pipeline_id) WHERE (processed = false);

View File

@ -467,6 +467,7 @@ control over how the Pages daemon runs and serves content in your environment.
| `rate_limit_tls_source_ip_burst` | Rate limit per source IP maximum TLS connections burst allowed per second. |
| `rate_limit_tls_domain` | Rate limit per domain in number of TLS connections per second. Set to `0` to disable this feature. |
| `rate_limit_tls_domain_burst` | Rate limit per domain maximum TLS connections burst allowed per second. |
| `rate_limit_subnets_allow_list` | Allow list with the IP ranges (subnets) that should bypass all rate limits. For example, `['1.2.3.4/24', '2001:db8::1/32']`. [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/14653) in GitLab 17.3. |
| `server_read_timeout` | Maximum duration to read the request headers and body. For no timeout, set to `0` or a negative value. Default: `5s` |
| `server_read_header_timeout` | Maximum duration to read the request headers. For no timeout, set to `0` or a negative value. Default: `1s` |
| `server_write_timeout` | Maximum duration to write all files in the response. Larger files require more time. For no timeout, set to `0` or a negative value. Default: `0` |

View File

@ -22174,6 +22174,17 @@ Check permissions for the current user on an epic.
| <a id="epicpermissionsreadepiciid"></a>`readEpicIid` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_epic_iid` on this resource. |
| <a id="epicpermissionsupdateepic"></a>`updateEpic` | [`Boolean!`](#boolean) | If `true`, the user can perform `update_epic` on this resource. |
### `EpssType`
Represents a CVE's EPSS score.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="epsstypecve"></a>`cve` | [`String!`](#string) | CVE identifier of relevant vulnerability. |
| <a id="epsstypescore"></a>`score` | [`Float!`](#float) | EPSS score for the CVE. |
### `EscalationPolicyType`
Represents an escalation policy.
@ -25451,6 +25462,7 @@ Defines which user roles, users, or groups can merge into a protected branch.
| <a id="mergerequestavailableautomergestrategies"></a>`availableAutoMergeStrategies` | [`[String!]`](#string) | Array of available auto merge strategies. |
| <a id="mergerequestawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of emoji reactions associated with the merge request. (see [Connections](#connections)) |
| <a id="mergerequestblockingmergerequests"></a>`blockingMergeRequests` **{warning-solid}** | [`BlockingMergeRequests`](#blockingmergerequests) | **Introduced** in GitLab 16.5. **Status**: Experiment. Merge requests that block another merge request from merging. |
| <a id="mergerequestclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the merge request was closed, null if not closed. |
| <a id="mergerequestcodequalityreportscomparer"></a>`codequalityReportsComparer` | [`CodequalityReportsComparer`](#codequalityreportscomparer) | Code quality reports comparison reported on the merge request. |
| <a id="mergerequestcommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
| <a id="mergerequestcommitcount"></a>`commitCount` | [`Int`](#int) | Number of commits in the merge request. |
@ -33905,6 +33917,7 @@ Represents a vulnerability.
| <a id="vulnerabilitydismissalreason"></a>`dismissalReason` | [`VulnerabilityDismissalReason`](#vulnerabilitydismissalreason) | Reason for dismissal. Returns `null` for states other than `dismissed`. |
| <a id="vulnerabilitydismissedat"></a>`dismissedAt` | [`Time`](#time) | Timestamp of when the vulnerability state was changed to dismissed. |
| <a id="vulnerabilitydismissedby"></a>`dismissedBy` | [`UserCore`](#usercore) | User that dismissed the vulnerability. |
| <a id="vulnerabilityepss"></a>`epss` | [`EpssType`](#epsstype) | EPSS score for CVE vulnerabilities. |
| <a id="vulnerabilityexternalissuelinks"></a>`externalIssueLinks` | [`VulnerabilityExternalIssueLinkConnection!`](#vulnerabilityexternalissuelinkconnection) | List of external issue links related to the vulnerability. (see [Connections](#connections)) |
| <a id="vulnerabilityfalsepositive"></a>`falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. |
| <a id="vulnerabilityhasremediations"></a>`hasRemediations` | [`Boolean`](#boolean) | Indicates whether there is a remediation available for this vulnerability. |

View File

@ -872,6 +872,36 @@ Get the GitHub integration settings for a project.
GET /projects/:id/integrations/github
```
## GitLab for Jira Cloud app
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/460663) in GitLab 17.2 [with a flag](../administration/feature_flags.md) named `enable_jira_connect_configuration`. Disabled by default.
The GitLab for Jira Cloud app integration is enabled or disabled automatically through [group linking and unlinking in Jira](../integration/jira/connect-app.md#configure-the-gitlab-for-jira-cloud-app). You cannot enable or disable the integration with the GitLab integrations form or the API.
### Update integration for a project
Use this API endpoint to update an integration you create with group linking in Jira.
```plaintext
PUT /projects/:id/integrations/jira-cloud-app
```
Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `jira_cloud_app_service_ids` | string | no | Jira Service Management Service IDs. Use commas (`,`) to separate multiple IDs. |
| `jira_cloud_app_enable_deployment_gating` | boolean | no | Enables deployment gating for blocked GitLab deployments from Jira Service Management. |
| `jira_cloud_app_deployment_gating_environments` | string | no | The environments (production, staging, testing, or development) to enable deployment gating. Required if deployment gating is enabled. Use commas (`,`) to separate multiple environments. |
### Get GitLab for Jira Cloud app settings
Get the GitLab for Jira Cloud app integration settings for a project.
```plaintext
GET /projects/:id/integrations/jira-cloud-app
```
## GitLab for Slack app
> - `use_inherited_settings` parameter [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/467089) in GitLab 17.2 [with a flag](../administration/feature_flags.md) named `integration_api_inheritance`. Disabled by default.

View File

@ -70,7 +70,7 @@ GET /projects
| `last_activity_before` | datetime | No | Limit results to projects with last activity before specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) |
| `membership` | boolean | No | Limit by projects that the current user is a member of. |
| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `last_activity_at`, or `similarity` fields. `repository_size`, `storage_size`, `packages_size` or `wiki_size` fields are only allowed for administrators. `similarity` is only available when searching and is limited to projects that the current user is a member of. Default is `created_at`. |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `star_count`, `last_activity_at`, or `similarity` fields. `repository_size`, `storage_size`, `packages_size` or `wiki_size` fields are only allowed for administrators. `similarity` is only available when searching and is limited to projects that the current user is a member of. Default is `created_at`. |
| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
| `repository_checksum_failed` | boolean | No | Limit projects where the repository checksum calculation has failed. Premium and Ultimate only. |
| `repository_storage` | string | No | Limit results to projects stored on `repository_storage`. _(administrators only)_ |
@ -343,7 +343,7 @@ GET /users/:user_id/projects
| `id_before` | integer | No | Limit results to projects with IDs less than the specified ID. |
| `membership` | boolean | No | Limit by projects that the current user is a member of. |
| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at`. |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `star_count`, or `last_activity_at` fields. Default is `created_at`. |
| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
| `search` | string | No | Return list of projects matching the search criteria. |
| `simple` | boolean | No | Return only limited fields for each project. Without authentication, this operation is a no-op; only simple fields are returned. |
@ -620,7 +620,7 @@ GET /users/:user_id/contributed_projects
| Attribute | Type | Required | Description |
|------------|---------|----------|-------------|
| `user_id` | string | Yes | The ID or username of the user. |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at`. |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `star_count`, or `last_activity_at` fields. Default is `created_at`. |
| `simple` | boolean | No | Return only limited fields for each project. Without authentication, this operation is a no-op; only simple fields are returned. |
| `sort` | string | No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
@ -868,7 +868,7 @@ GET /users/:user_id/starred_projects
| `archived` | boolean | No | Limit by archived status. |
| `membership` | boolean | No | Limit by projects that the current user is a member of. |
| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at`. |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `star_count`, or `last_activity_at` fields. Default is `created_at`. |
| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
| `search` | string | No | Return list of projects matching the search criteria. |
| `simple` | boolean | No | Return only limited fields for each project. Without authentication, this operation is a no-op; only simple fields are returned. |
@ -1894,7 +1894,7 @@ GET /projects/:id/forks
| `archived` | boolean | No | Limit by archived status. |
| `membership` | boolean | No | Limit by projects that the current user is a member of. |
| `min_access_level` | integer | No | Limit by current user minimal [role (`access_level`)](members.md#roles). |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at`. |
| `order_by` | string | No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, `star_count`, or `last_activity_at` fields. Default is `created_at`. |
| `owned` | boolean | No | Limit by projects explicitly owned by the current user. |
| `search` | string | No | Return list of projects matching the search criteria. |
| `simple` | boolean | No | Return only limited fields for each project. Without authentication, this operation is a no-op; only simple fields are returned. |
@ -3472,7 +3472,7 @@ GET /projects
| Attribute | Type | Required | Description |
|------------|--------|----------|-------------|
| `search` | string | Yes | A string contained in the project name. |
| `order_by` | string | No | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields. |
| `order_by` | string | No | Return requests ordered by `id`, `name`, `created_at`, `star_count`, or `last_activity_at` fields. |
| `sort` | string | No | Return requests sorted in `asc` or `desc` order. |
```shell

View File

@ -10,16 +10,7 @@ module API
NO_BROWSER_EXECUTION_RESPONSE_HEADERS = { 'Content-Security-Policy' => "default-src 'none'" }.freeze
MAJOR_BROWSERS = %i[webkit firefox ie edge opera chrome].freeze
WEB_BROWSER_ERROR_MESSAGE = 'This endpoint is not meant to be accessed by a web browser.'
TIMEOUTS = {
open: 10,
read: 10
}.freeze
RESPONSE_STATUSES = {
error: :bad_gateway,
timeout: :gateway_timeout
}.freeze
UPSTREAM_GID_HEADER = 'X-Gitlab-Virtual-Registry-Upstream-Global-Id'
included do
helpers do
@ -31,8 +22,8 @@ module API
def send_successful_response_from(service_response:)
action, action_params = service_response.to_h.values_at(:action, :action_params)
case action
when :workhorse_send_url
workhorse_send_url(url: action_params[:url], headers: action_params[:headers])
when :workhorse_upload_url
workhorse_upload_url(**action_params.slice(:url, :upstream))
end
end
@ -47,15 +38,13 @@ module API
end
end
def workhorse_send_url(url:, headers: {})
def workhorse_upload_url(url:, upstream:)
send_workhorse_headers(
Gitlab::Workhorse.send_url(
Gitlab::Workhorse.send_dependency(
upstream.headers,
url,
headers: headers,
allow_redirects: true,
timeouts: TIMEOUTS,
response_statuses: RESPONSE_STATUSES,
response_headers: NO_BROWSER_EXECUTION_RESPONSE_HEADERS
response_headers: NO_BROWSER_EXECUTION_RESPONSE_HEADERS,
upload_config: { headers: { UPSTREAM_GID_HEADER => upstream.to_global_id.to_s } }
)
)
end

View File

@ -78,7 +78,8 @@ module API
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
{ code: 404, message: 'Not found' },
{ code: 422, message: 'Unprocessable entity' }
]
tags INTEGRATIONS_TAGS
end
@ -98,18 +99,18 @@ module API
integration = user_project.find_or_initialize_integration(slug.underscore)
render_api_error!('400 Integration not available', 400) if integration.nil?
params = declared_params(include_missing: false).merge(active: true)
if integration.is_a?(::Integrations::GitlabSlackApplication)
unless integration.manual_activation? || integration.is_a?(::Integrations::Prometheus)
if integration.new_record?
render_api_error!('You cannot create the GitLab for Slack app from the API', 422)
render_api_error!("You cannot create the #{integration.class.title} integration from the API", 422)
end
params.delete(:active)
end
render_api_error!('400 Bad Request', 400) if integration.nil?
result = ::Integrations::UpdateService.new(
current_user: current_user, integration: integration, attributes: params
).execute
@ -144,6 +145,10 @@ module API
not_found!('Integration') unless integration&.persisted?
if integration.is_a?(::Integrations::JiraCloudApp)
render_api_error!("You cannot disable the #{integration.class.title} integration from the API", 422)
end
destroy_conditionally!(integration) do
attrs = integration_attributes(integration).index_with do |attr|
column = if integration.attribute_present?(attr)

View File

@ -119,7 +119,7 @@ module API
params :sort_params do
optional :order_by, type: String,
values: %w[id name path created_at updated_at last_activity_at similarity] + Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS,
values: %w[id name path created_at updated_at last_activity_at similarity star_count] + Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS,
default: 'created_at', desc: "Return projects ordered by field. #{Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS.join(', ')} are only available to admins. Similarity is available when searching and is limited to projects the user has access to."
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return projects sorted in ascending and descending order'

View File

@ -9,6 +9,8 @@ module API
feature_category :virtual_registry
urgency :low
MAX_FILE_SIZE = 5.gigabytes
authenticate_with do |accept|
accept.token_types(:personal_access_token).sent_through(:http_private_token_header)
accept.token_types(:deploy_token).sent_through(:http_deploy_token_header)
@ -34,6 +36,17 @@ module API
::VirtualRegistries::Packages::Maven::Registry.find(params[:id])
end
strong_memoize_attr :registry
params :id_and_path do
requires :id,
type: Integer,
desc: 'The ID of the Maven virtual registry'
requires :path,
type: String,
file_path: true,
desc: 'Package path',
documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT/mypkg-1.0-SNAPSHOT.jar' }
end
end
after_validation do
@ -61,45 +74,116 @@ module API
end
end
desc 'Download endpoint of the Maven virtual registry.' do
detail 'This feature was introduced in GitLab 17.3. \
This feature is currently in experiment state. \
This feature behind the `virtual_registry_maven` feature flag.'
success [
{ code: 200 }
]
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[maven_virtual_registries]
hidden true
end
params do
requires :id,
type: Integer,
desc: 'The ID of the Maven virtual registry'
requires :path,
type: String,
file_path: true,
desc: 'Package path',
documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT/mypkg-1.0-SNAPSHOT.jar' }
end
namespace ':id/*path' do
include ::API::Concerns::VirtualRegistries::Packages::Endpoint
desc 'Download endpoint of the Maven virtual registry.' do
detail 'This feature was introduced in GitLab 17.3. \
This feature is currently in experiment state. \
This feature is behind the `virtual_registry_maven` feature flag.'
success [
{ code: 200 }
]
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[maven_virtual_registries]
hidden true
end
params do
use :id_and_path
end
get format: false do
service_response = ::VirtualRegistries::Packages::Maven::HandleFileRequestService.new(
registry: registry,
current_user: current_user,
params: { path: params[:path] }
params: { path: declared_params[:path] }
).execute
send_error_response_from!(service_response: service_response) if service_response.error?
send_successful_response_from(service_response: service_response)
end
namespace 'upload' do
after_validation do
require_gitlab_workhorse!
authorize!(:read_virtual_registry, registry)
end
desc 'Workhorse authorize upload endpoint of the Maven virtual registry. Only workhorse can access it.' do
detail 'This feature was introduced in GitLab 17.4. \
This feature is currently in experiment state. \
This feature is behind the `virtual_registry_maven` feature flag.'
success [
{ code: 200 }
]
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[maven_virtual_registries]
hidden true
end
params do
use :id_and_path
end
post 'authorize' do
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
::VirtualRegistries::CachedResponseUploader.workhorse_authorize(has_length: true,
maximum_size: MAX_FILE_SIZE)
end
desc 'Workhorse upload endpoint of the Maven virtual registry. Only workhorse can access it.' do
detail 'This feature was introduced in GitLab 17.4. \
This feature is currently in experiment state. \
This feature is behind the `virtual_registry_maven` feature flag.'
success [
{ code: 200 }
]
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[maven_virtual_registries]
hidden true
end
params do
use :id_and_path
requires :file,
type: ::API::Validations::Types::WorkhorseFile,
desc: 'The file being uploaded',
documentation: { type: 'file' }
end
post do
etag, content_type, upstream_gid = request.headers.fetch_values(
'Etag',
::Gitlab::Workhorse::SEND_DEPENDENCY_CONTENT_TYPE_HEADER,
UPSTREAM_GID_HEADER
) { nil }
# TODO: revisit this part when multiple upstreams are supported
# https://gitlab.com/gitlab-org/gitlab/-/issues/480461
# coherence check
not_found!('Upstream') unless upstream == GlobalID::Locator.locate(upstream_gid)
service_response = ::VirtualRegistries::Packages::Maven::CachedResponses::CreateService.new(
upstream: upstream,
current_user: current_user,
params: declared_params.merge(etag: etag, content_type: content_type)
).execute
send_error_response_from!(service_response: service_response) if service_response.error?
created!
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Background migration to set the vulnerability count in the project_statistics table.
class SetProjectVulnerabilityCount < BatchedMigrationJob
feature_category :vulnerability_management
def perform; end
end
end
end
Gitlab::BackgroundMigration::SetProjectVulnerabilityCount.prepend_mod

View File

@ -16,7 +16,7 @@ module Gitlab
return false if streaming?
return false if Gitlab::Utils.to_boolean(params[:no_pagination], default: false)
Feature.enabled?(:blame_page_pagination, project)
true
end
def full?

View File

@ -6,12 +6,11 @@ module Gitlab
LAMBDA_FOR_UNIQUE_USERNAME = ->(username) { User.username_exists?(username) }.freeze
LAMBDA_FOR_UNIQUE_EMAIL = ->(email) { User.find_by_email(email) || ::Email.find_by_email(email) }.freeze
def initialize(import_type:, source_hostname:, source_name:, source_username:, namespace:)
@import_type = import_type
@source_hostname = source_hostname
@source_name = source_name
@source_username = source_username
@namespace = namespace
delegate :import_type, :namespace, :source_user_identifier, :source_name, :source_username, to: :source_user,
private: true
def initialize(source_user)
@source_user = source_user
end
def execute
@ -30,7 +29,7 @@ module Gitlab
private
attr_reader :import_type, :namespace, :source_hostname, :source_name, :source_username
attr_reader :source_user
def placeholder_name
# Some APIs don't expose users' names, so set a default if it's nil
@ -47,7 +46,7 @@ module Gitlab
end
def placeholder_email
email_pattern = "#{valid_username_segment}_placeholder_user_%s@#{Settings.gitlab.host}"
email_pattern = "#{fallback_username_segment}_%s@#{Settings.gitlab.host}"
uniquify_string(email_pattern, LAMBDA_FOR_UNIQUE_EMAIL)
end
@ -61,8 +60,13 @@ module Gitlab
sanitized_source_username.slice(0, User::MAX_USERNAME_LENGTH - 55)
end
# Returns a string based on the import type, and digest of namespace path and source user identifier.
# Example: "gitlab_migration_64c4f07e"
def fallback_username_segment
"#{namespace.path}_#{import_type}"
@fallback_username_segment ||= [
import_type,
Zlib.crc32([namespace.path, source_user_identifier].join).to_s(16)
].join('_')
end
def uniquify_string(base_pattern, lambda_for_uniqueness)

View File

@ -71,22 +71,16 @@ module Gitlab
source_hostname: source_hostname
)
import_source_user.placeholder_user = create_placeholder_user(source_name, source_username)
import_source_user.placeholder_user = create_placeholder_user(import_source_user)
import_source_user.save!
import_source_user
end
end
def create_placeholder_user(source_name, source_username)
def create_placeholder_user(import_source_user)
return namespace_import_user if placeholder_user_limit_exceeded?
Gitlab::Import::PlaceholderUserCreator.new(
import_type: import_type,
source_hostname: source_hostname,
source_name: source_name,
source_username: source_username,
namespace: namespace
).execute
Gitlab::Import::PlaceholderUserCreator.new(import_source_user).execute
end
def namespace_import_user

View File

@ -35613,6 +35613,60 @@ msgstr ""
msgid "New! Suggest changes directly"
msgstr ""
msgid "NewFramework|All todos done."
msgstr ""
msgid "NewFramework|Apply to projects"
msgstr ""
msgid "NewFramework|Back to compliance center"
msgstr ""
msgid "NewFramework|Compliance framework created!"
msgstr ""
msgid "NewFramework|Feedback?"
msgstr ""
msgid "NewFramework|Go to the %{linkStart}compliance center%{linkEnd} to apply this framework to projects."
msgstr ""
msgid "NewFramework|Go to the %{linkStart}policy management page%{linkEnd} to scope a policy for this framework."
msgstr ""
msgid "NewFramework|Have questions or thoughts on the new improvements we made? %{linkStart}Please provide feedback on your experience%{linkEnd}."
msgstr ""
msgid "NewFramework|How do I apply the framework to projects?"
msgstr ""
msgid "NewFramework|How to scope policies?"
msgstr ""
msgid "NewFramework|New improvements to creating compliance framework."
msgstr ""
msgid "NewFramework|Policies scoped to a framework serve as solutions to specific compliance requirements and are applied to projects."
msgstr ""
msgid "NewFramework|Projects that have this framework applied are automatically checked against requirements and have policies enforced."
msgstr ""
msgid "NewFramework|Scope policies"
msgstr ""
msgid "NewFramework|Suggested next steps"
msgstr ""
msgid "NewFramework|Use the compliance framework to scope policies and include projects to make sure they are compliant."
msgstr ""
msgid "NewFramework|Why apply the framework to projects?"
msgstr ""
msgid "NewFramework|Why scope policies?"
msgstr ""
msgid "Newest first"
msgstr ""
@ -64144,12 +64198,6 @@ msgstr[1] ""
msgid "finding is not found or is already attached to a vulnerability"
msgstr ""
msgid "for Workspace must have an associated WorkspacesAgentConfig"
msgstr ""
msgid "for Workspace must match the dns_zone of the associated WorkspacesAgentConfig"
msgstr ""
msgid "for this project"
msgstr ""
@ -64418,6 +64466,18 @@ msgstr ""
msgid "key result"
msgstr ""
msgid "key: %{key} is reserved for internal usage"
msgstr ""
msgid "key: %{key} must be a string"
msgstr ""
msgid "key: %{key} must have name component with 63 characters or less, and start/end with an alphanumeric character"
msgstr ""
msgid "key: %{key} must have prefix component with 253 characters or less, and have a valid DNS subdomain as a prefix"
msgstr ""
msgid "kuromoji custom analyzer"
msgstr ""
@ -64869,6 +64929,9 @@ msgstr ""
msgid "must be a root namespace"
msgstr ""
msgid "must be a string"
msgstr ""
msgid "must be a valid IPv4 or IPv6 address"
msgstr ""
@ -64899,6 +64962,9 @@ msgstr ""
msgid "must be an email you have verified"
msgstr ""
msgid "must be an hash"
msgstr ""
msgid "must be associated with a Group or a Project"
msgstr ""
@ -65530,6 +65596,12 @@ msgstr ""
msgid "value for '%{storage}' must be between 0 and 100"
msgstr ""
msgid "value: %{value} must be 63 characters or less, and start/end with an alphanumeric character"
msgstr ""
msgid "value: %{value} must be a string"
msgstr ""
msgid "verify ownership"
msgstr ""

View File

@ -305,7 +305,7 @@
"swagger-cli": "^4.0.4",
"tailwindcss": "^3.4.1",
"timezone-mock": "^1.0.8",
"vite": "^5.4.2",
"vite": "^5.4.3",
"vite-plugin-ruby": "^5.0.0",
"vue-loader-vue3": "npm:vue-loader@17.4.2",
"vue-test-utils-compat": "0.0.14",

View File

@ -75,6 +75,7 @@ RSpec.describe 'Database schema', feature_category: :database do
ci_builds: %w[project_id runner_id user_id erased_by_id trigger_request_id partition_id auto_canceled_by_partition_id execution_config_id upstream_pipeline_partition_id],
ci_builds_metadata: %w[partition_id project_id build_id],
ci_daily_build_group_report_results: %w[partition_id],
ci_deleted_objects: %w[project_id],
ci_job_artifacts: %w[partition_id project_id job_id],
ci_namespace_monthly_usages: %w[namespace_id],
ci_pipeline_artifacts: %w[partition_id],

View File

@ -5,5 +5,6 @@ FactoryBot.define do
pick_up_at { Time.current }
store_dir { SecureRandom.uuid }
file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') }
project_id { FactoryBot.create(:project).id }
end
end

View File

@ -139,6 +139,13 @@ FactoryBot.define do
deployment_type { 'cloud' }
end
trait :jira_cloud do
url { 'https://mysite.atlassian.net' }
username { 'jira_user' }
password { 'my-secret-password' }
jira_auth_type { 0 }
end
after(:build) do |integration, evaluator|
integration.instance_variable_set(:@old_data_fields, nil)
@ -250,13 +257,6 @@ FactoryBot.define do
external_wiki_url { 'http://external-wiki-url.com' }
end
trait :jira_cloud_service do
url { 'https://mysite.atlassian.net' }
username { 'jira_user' }
password { 'my-secret-password' }
jira_auth_type { 0 }
end
trait :chat_notification do
sequence(:webhook) { |n| "https://example.com/webhook/#{n}" }
push_events { false }

View File

@ -10,7 +10,7 @@ FactoryBot.define do
create(:board, project: projects[0])
create(:jira_integration, project: projects[0])
create(:jira_integration, :without_properties_callback, project: projects[1])
create(:jira_integration, :jira_cloud_service, project: projects[2])
create(:jira_integration, :jira_cloud, project: projects[2])
create(:jira_integration, :without_properties_callback, project: projects[3], properties: { url: 'https://mysite.atlassian.net' })
jira_label = create(:label, project: projects[0])
create(:jira_import_state, :finished, project: projects[0], label: jira_label, failed_to_import_count: 2, imported_issues_count: 7, total_issue_count: 9)

View File

@ -115,22 +115,6 @@ RSpec.describe 'File blame', :js, feature_category: :source_code_management do
expect(page).to have_text('Loading full blame...')
end
end
context 'when feature flag disabled' do
before do
stub_feature_flags(blame_page_pagination: false)
end
it 'displays the blame page without pagination' do
visit_blob_blame(path)
within_testid 'blob-content-holder' do
expect(page).to have_css('.blame-commit')
expect(page).not_to have_css('.gl-pagination')
expect(page).not_to have_link _('Show full blame')
end
end
end
end
context 'when blob length is over global max page limit' do

View File

@ -165,7 +165,7 @@ describe('View branch rules', () => {
jest.spyOn(util, 'getParameterByName').mockReturnValueOnce(ALL_BRANCHES_WILDCARD);
await createComponent();
expect(findAllBranches().text()).toBe(I18N.allBranches);
expect(findAllBranches().text()).toBe('*');
});
it('renders matching branches link', () => {

View File

@ -24,6 +24,7 @@ import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { mockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import SearchItem from '~/super_sidebar/components/global_search/command_palette/search_item.vue';
import { COMMANDS, LINKS, USERS, FILES, SETTINGS } from './mock_data';
const links = LINKS.reduce(linksReducer, []);
@ -47,6 +48,7 @@ describe('CommandPaletteItems', () => {
stubs: {
GlDisclosureDropdownGroup,
GlDisclosureDropdownItem,
SearchItem,
},
provide: {
commandPaletteCommands: COMMANDS,
@ -325,5 +327,24 @@ describe('CommandPaletteItems', () => {
label,
});
});
it('tracks command settings', async () => {
createComponent({ handle: COMMAND_HANDLE });
await waitForPromises();
wrapper.setProps({ searchQuery: 'ava' });
await waitForPromises();
trackingSpy.mockClear();
findGroups().at(0).vm.$emit('action', { text: 'Avatar' });
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'click_project_setting_in_command_palette',
expect.objectContaining({
label: 'Avatar',
}),
);
});
});
});

View File

@ -32,7 +32,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'], feature_category: :code_revie
source_branch_exists target_branch_exists diverged_from_target_branch
upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees reviewers participants subscribed labels discussion_locked time_estimate
total_time_spent human_time_estimate human_total_time_spent reference author merged_at
total_time_spent human_time_estimate human_total_time_spent reference author merged_at closed_at
commit_count current_user_todos conflicts auto_merge_enabled approved_by source_branch_protected
squash_on_merge available_auto_merge_strategies
has_ci mergeable commits committers commits_without_merge_commits squash security_auto_fix default_squash_commit_message

View File

@ -36,27 +36,11 @@ RSpec.describe Gitlab::Git::BlameMode, feature_category: :source_code_management
it { is_expected.to be_falsey }
end
context 'when `blame_page_pagination` is disabled' do
before do
stub_feature_flags(blame_page_pagination: false)
end
it { is_expected.to be_falsey }
end
end
describe '#full?' do
subject { blame_mode.full? }
it { is_expected.to be_falsey }
context 'when `blame_page_pagination` is disabled' do
before do
stub_feature_flags(blame_page_pagination: false)
end
it { is_expected.to be_truthy }
end
end
end

View File

@ -10,17 +10,21 @@ RSpec.describe Gitlab::Import::PlaceholderUserCreator, feature_category: :import
let(:source_hostname) { 'github.com' }
let(:source_name) { 'Pry Contributor' }
let(:source_username) { 'a_pry_contributor' }
let(:source_user_identifier) { '1' }
subject(:service) do
described_class.new(
let(:source_user) do
build(:import_source_user,
import_type: import_type,
source_hostname: source_hostname,
source_name: source_name,
source_username: source_username,
source_user_identifier: source_user_identifier,
namespace: namespace
)
end
subject(:service) { described_class.new(source_user) }
it 'creates one new placeholder user with a unique email and username' do
expect { service.execute }.to change { User.where(user_type: :placeholder).count }.from(0).to(1)
@ -28,7 +32,7 @@ RSpec.describe Gitlab::Import::PlaceholderUserCreator, feature_category: :import
expect(new_placeholder_user.name).to eq("Placeholder #{source_name}")
expect(new_placeholder_user.username).to match(/^aprycontributor_placeholder_user_\d+$/)
expect(new_placeholder_user.email).to match(/^aprycontributor_placeholder_user_\d+@#{Settings.gitlab.host}$/)
expect(new_placeholder_user.email).to match(/^#{import_type}_\h+_\d+@#{Settings.gitlab.host}$/)
expect(new_placeholder_user.namespace.organization).to eq(namespace.organization)
end
@ -44,12 +48,14 @@ RSpec.describe Gitlab::Import::PlaceholderUserCreator, feature_category: :import
context 'when generating a unique email address' do
it 'validates against all stored email addresses' do
existing_user = create(:user, email: 'aprycontributor_placeholder_user_1@localhost')
existing_user.emails.create!(email: 'aprycontributor_placeholder_user_2@localhost')
allow(Zlib).to receive(:crc32).and_return(123)
existing_user = create(:user, email: 'github_7b_1@localhost')
existing_user.emails.create!(email: 'github_7b_2@localhost')
placeholder_user = service.execute
expect(placeholder_user.email).to eq('aprycontributor_placeholder_user_3@localhost')
expect(placeholder_user.email).to eq('github_7b_3@localhost')
end
end
@ -79,7 +85,7 @@ RSpec.describe Gitlab::Import::PlaceholderUserCreator, feature_category: :import
let(:source_username) { nil }
it 'assigns a default username' do
expected_match = /^#{namespace.path}_#{import_type}_placeholder_user_\d+$/
expected_match = /^#{import_type}_\h+_placeholder_user_\d+$/
placeholder_user = service.execute
@ -93,7 +99,7 @@ RSpec.describe Gitlab::Import::PlaceholderUserCreator, feature_category: :import
where(:input_username, :expected_output) do
'.asdf' | /^asdf_placeholder_user_1$/
'asdf^ghjk' | /^asdfghjk_placeholder_user_1$/
'.' | /^\w+_github_placeholder_user_1$/
'.' | /^#{import_type}_\h+_placeholder_user_1$/
end
with_them do

View File

@ -61,7 +61,7 @@ RSpec.describe Gitlab::Import::SourceUserMapper, :request_store, feature_categor
expect(new_placeholder_user.name).to eq("Placeholder #{source_name}")
expect(new_placeholder_user.username).to match(/^aprycontributor_placeholder_user_\d+$/)
expect(new_placeholder_user.email).to match(/^aprycontributor_placeholder_user_\d+@#{Settings.gitlab.host}$/)
expect(new_placeholder_user.email).to match(/^#{import_type}_\h+_\d+@#{Settings.gitlab.host}$/)
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueSetProjectVulnerabilityCount, feature_category: :vulnerability_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :project_settings,
column_name: :project_id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -0,0 +1,91 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe CapWorkspacesMaxTerminationToOneYear, feature_category: :remote_development do
let(:user) { table(:users).create!(name: 'test', email: 'test@example.com', projects_limit: 5) }
let(:namespace) { table(:namespaces).create!(name: 'name', path: 'path') }
let(:project) { table(:projects).create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
let!(:personal_access_token) do
table(:personal_access_tokens).create!(
user_id: user.id,
name: 'token_name',
expires_at: Time.now
)
end
let(:cluster_agent) do
table(:cluster_agents).create!(
id: 1,
name: 'Agent-1',
project_id: project.id
)
end
let!(:agent) do
table(:remote_development_agent_configs).create!(
cluster_agent_id: cluster_agent.id,
enabled: true,
dns_zone: 'test.workspace.me',
project_id: project.id
)
end
let!(:workspace_1) do
table(:workspaces).create!(
user_id: user.id,
project_id: project.id,
cluster_agent_id: cluster_agent.id,
desired_state_updated_at: Time.now,
responded_to_agent_at: Time.now,
name: 'workspace-1',
namespace: 'workspace_1_namespace',
desired_state: 'Terminated',
actual_state: 'Terminated',
editor: 'vs-code',
devfile_ref: 'devfile-ref',
devfile_path: 'devfile-path',
devfile: 'devfile',
processed_devfile: 'processed_dev_file',
url: 'workspace-url',
deployment_resource_version: 'v1',
personal_access_token_id: personal_access_token.id,
max_hours_before_termination: 5760
)
end
let!(:workspace_2) do
table(:workspaces).create!(
user_id: user.id,
project_id: project.id,
cluster_agent_id: cluster_agent.id,
desired_state_updated_at: Time.now,
responded_to_agent_at: Time.now,
name: 'workspace-2',
namespace: 'workspace_2_namespace',
desired_state: 'Running',
actual_state: 'Running',
editor: 'vs-code',
devfile_ref: 'devfile-ref',
devfile_path: 'devfile-path',
devfile: 'devfile',
processed_devfile: 'processed_dev_file',
url: 'workspace-url',
deployment_resource_version: 'v1',
personal_access_token_id: personal_access_token.id,
max_hours_before_termination: 8761
)
end
context 'when there exist workspace whose have a termination limit grater than the max and some less than the max' do
it 'caps those that exceed the max and leaves the rest unchanged' do
expect do
migrate!
end.to not_change { workspace_1.reload.max_hours_before_termination }.and(
change { workspace_2.reload.max_hours_before_termination }.from(8761).to(8760)
)
end
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Ci::DeletedObject, :aggregate_failures do
RSpec.describe Ci::DeletedObject, :aggregate_failures, feature_category: :job_artifacts do
describe 'attributes' do
it { is_expected.to respond_to(:file) }
it { is_expected.to respond_to(:store_dir) }
@ -23,6 +23,7 @@ RSpec.describe Ci::DeletedObject, :aggregate_failures do
expect(deleted_artifact.store_dir).to eq(artifact.file.store_dir.to_s)
expect(deleted_artifact.file_identifier).to eq(artifact.file_identifier)
expect(deleted_artifact.pick_up_at).to be_like_time(artifact.expire_at)
expect(deleted_artifact.project_id).to eq(artifact.project_id)
end
end

View File

@ -843,6 +843,10 @@ RSpec.describe Ci::JobArtifact, feature_category: :job_artifacts do
expect(attributes[:file_store]).to eq(artifact.file_store)
end
it 'returns the project_id' do
expect(attributes[:project_id]).to eq(artifact.project_id)
end
context 'when pick_up_at is present' do
let(:pick_up_at) { 2.hours.ago }

View File

@ -115,6 +115,22 @@ RSpec.describe VirtualRegistries::Packages::Maven::CachedResponse, type: :model,
end
end
describe '#filename' do
let(:cached_response) { build(:virtual_registries_packages_maven_cached_response) }
subject { cached_response.filename }
it { is_expected.to eq(File.basename(cached_response.relative_path)) }
context 'when relative_path is nil' do
before do
cached_response.relative_path = nil
end
it { is_expected.to be_nil }
end
end
context 'with loose foreign key on virtual_registries_packages_maven_cached_responses.upstream_id' do
it_behaves_like 'cleanup by a loose foreign key' do
let_it_be(:parent) { create(:virtual_registries_packages_maven_upstream) }

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe VirtualRegistries::Packages::Maven::UpstreamPolicy, feature_category: :virtual_registry do
let_it_be(:upstream) { create(:virtual_registries_packages_maven_upstream) }
let(:user) { upstream.group.first_owner }
subject(:policy) { described_class.new(user, upstream) }
describe 'delegation' do
let(:delegations) { policy.delegated_policies }
it 'delegates to the registry policy' do
expect(delegations.size).to eq(1)
delegations.each_value do |delegated_policy|
expect(delegated_policy).to be_instance_of(::VirtualRegistries::Packages::Maven::RegistryPolicy)
end
end
end
end

View File

@ -195,22 +195,62 @@ RSpec.describe 'getting merge request information nested in a project', feature_
end
end
it 'includes correct mergedAt value when merged' do
time = 1.week.ago
merge_request.mark_as_merged
merge_request.metrics.update!(merged_at: time)
describe 'mergedAt' do
let(:merged_at) { merge_request_graphql_data['mergedAt'] }
post_graphql(query, current_user: current_user)
retrieved = merge_request_graphql_data['mergedAt']
subject(:graphql_query) { post_graphql(query, current_user: current_user) }
expect(Time.zone.parse(retrieved)).to be_within(1.second).of(time)
context 'when the merge request is merged' do
let_it_be(:time) { 1.week.ago }
before do
merge_request.mark_as_merged
merge_request.metrics.update!(merged_at: time)
end
it 'includes correct mergedAt value' do
graphql_query
expect(Time.zone.parse(merged_at)).to be_within(1.second).of(time)
end
end
context 'when the merge request is not merged' do
it 'includes nil mergedAt value' do
graphql_query
expect(merged_at).to be_nil
end
end
end
it 'includes nil mergedAt value when not merged' do
post_graphql(query, current_user: current_user)
retrieved = merge_request_graphql_data['mergedAt']
describe 'closedAt' do
let(:closed_at) { merge_request_graphql_data['closedAt'] }
expect(retrieved).to be_nil
subject(:graphql_query) { post_graphql(query, current_user: current_user) }
context 'when the merge request is closed' do
let_it_be(:time) { 1.week.ago }
before do
merge_request.close!
merge_request.metrics.update!(latest_closed_at: time)
end
it 'includes correct closedAt value' do
graphql_query
expect(Time.zone.parse(closed_at)).to be_within(1.second).of(time)
end
end
context 'when the merge request is not closed' do
it 'includes nil closedAt value' do
graphql_query
expect(closed_at).to be_nil
end
end
end
describe 'permissions on the merge request' do

View File

@ -300,13 +300,25 @@ RSpec.describe 'getting merge request listings nested in a project', feature_cat
context 'when requesting `merged_at`' do
let(:requested_fields) { [:merged_at] }
let(:merge_request_ids) { [merge_request_a.id, merge_request_b.id, merge_request_c.id] }
before do
# make the MRs "merged"
[merge_request_a, merge_request_b, merge_request_c].each do |mr|
mr.update!(state_id: MergeRequest.available_states[:merged])
mr.metrics.update!(merged_at: Time.now)
end
::MergeRequest.where(id: merge_request_ids).update_all(state_id: MergeRequest.available_states[:merged])
::MergeRequest::Metrics.where(merge_request_id: merge_request_ids).update_all(merged_at: Time.now)
end
include_examples 'N+1 query check'
end
context 'when requesting `closed_at`' do
let(:requested_fields) { [:closed_at] }
let(:merge_request_ids) { [merge_request_a.id, merge_request_b.id, merge_request_c.id] }
before do
# make the MRs "closed"
::MergeRequest.where(id: merge_request_ids).update_all(state_id: MergeRequest.available_states[:closed])
::MergeRequest::Metrics.where(merge_request_id: merge_request_ids).update_all(latest_closed_at: Time.now)
end
include_examples 'N+1 query check'

View File

@ -58,6 +58,7 @@ RSpec.describe API::Integrations, feature_category: :integrations do
# You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
unavailable_integration_names = [
Integrations::GitlabSlackApplication.to_param,
Integrations::JiraCloudApp.to_param,
Integrations::Zentao.to_param
]
@ -330,6 +331,7 @@ RSpec.describe API::Integrations, feature_category: :integrations do
put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API')
end
end
@ -366,6 +368,60 @@ RSpec.describe API::Integrations, feature_category: :integrations do
end
end
describe 'GitLab for Jira Cloud app integration' do
before do
stub_application_setting(jira_connect_application_key: 'mock_key')
create(:jira_cloud_app_integration, project: project)
end
describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do
context 'for integration creation' do
before do
project.jira_cloud_app_integration.destroy!
end
it 'returns 422' do
put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API')
end
end
context 'for integration update' do
before do
project.jira_cloud_app_integration.update!(active: false)
end
it "does not enable the integration" do
put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
expect(response).to have_gitlab_http_status(:ok)
expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false)
end
end
end
describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do
it "fetches the integration and returns the correct fields" do
get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
expect(response).to have_gitlab_http_status(:ok)
assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration)
end
end
describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do
it "does not disable the integration" do
expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) }
.not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true)
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API')
end
end
end
private
def assert_correct_response_fields(response_keys, integration)

View File

@ -893,6 +893,23 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
end
context 'sorting' do
context 'by star_count' do
let_it_be(:project_most_stars) { create(:project, :public, star_count: 100) }
let_it_be(:project_no_stars) { create(:project, :public, star_count: 0) }
let_it_be(:project_few_stars) { create(:project, :public, star_count: 10) }
it 'with order_by=star_count, returns list of projects sorted by star_count descending' do
get api(path), params: { order_by: 'star_count' }
expected_order = [project_most_stars, project_few_stars, project_no_stars, public_project]
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |x| x['id'] }).to eq(expected_order.map(&:id))
end
end
context 'by project statistics' do
%w[repository_size storage_size wiki_size packages_size].each do |order_by|
context "sorting by #{order_by}" do

View File

@ -46,6 +46,26 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
it_behaves_like 'returning response status', :unauthorized
end
shared_examples 'authenticated endpoint' do |success_shared_example_name:|
%i[personal_access_token deploy_token job_token].each do |token_type|
context "with a #{token_type}" do
let_it_be(:user) { deploy_token } if token_type == :deploy_token
context 'when sent by headers' do
let(:headers) { super().merge(token_header(token_type)) }
it_behaves_like success_shared_example_name
end
context 'when sent by basic auth' do
let(:headers) { super().merge(token_basic_auth(token_type)) }
it_behaves_like success_shared_example_name
end
end
end
end
before do
stub_config(dependency_proxy: { enabled: true }) # not enabled by default
end
@ -1183,8 +1203,10 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
let(:url) { "/virtual_registries/packages/maven/#{registry.id}/#{path}" }
let(:service_response) do
ServiceResponse.success(
payload: { action: :workhorse_send_url,
action_params: { url: upstream.url_for(path), headers: upstream.headers } }
payload: {
action: :workhorse_upload_url,
action_params: { url: upstream.url_for(path), upstream: upstream }
}
)
end
@ -1203,12 +1225,12 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
get api(url), headers: headers
end
shared_examples 'returning the workhorse send_url response' do
shared_examples 'returning the workhorse send_dependency response' do
it 'returns a workhorse send_url response' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('send-url:')
expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('send-dependency:')
expect(response.headers['Content-Type']).to eq('application/octet-stream')
expect(response.headers['Content-Length'].to_i).to eq(0)
expect(response.body).to eq('')
@ -1223,74 +1245,24 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
[value]
end
expect(send_data_type).to eq('send-url')
expect(send_data['URL']).to be_present
expect(send_data['AllowRedirects']).to be_truthy
expect(send_data['DialTimeout']).to eq('10s')
expect(send_data['ResponseHeaderTimeout']).to eq('10s')
expect(send_data['ErrorResponseStatus']).to eq(502)
expect(send_data['TimeoutResponseStatus']).to eq(504)
expect(send_data['Header']).to eq(expected_headers)
expected_upload_config = {
'Headers' => { described_class::UPSTREAM_GID_HEADER => [upstream.to_global_id.to_s] }
}
expect(send_data_type).to eq('send-dependency')
expect(send_data['Url']).to be_present
expect(send_data['Headers']).to eq(expected_headers)
expect(send_data['ResponseHeaders']).to eq(expected_resp_headers)
expect(send_data['UploadConfig']).to eq(expected_upload_config)
end
end
context 'for authentication' do
context 'with a personal access token' do
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
context 'when sent by headers' do
let(:headers) { { 'Private-Token' => personal_access_token.token } }
it_behaves_like 'returning the workhorse send_url response'
end
context 'when sent by basic auth' do
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'returning the workhorse send_url response'
end
it_behaves_like 'authenticated endpoint',
success_shared_example_name: 'returning the workhorse send_dependency response' do
let(:headers) { {} }
end
context 'with a deploy token' do
let_it_be(:deploy_token) do
create(:deploy_token, :group, groups: [registry.group], read_virtual_registry: true)
end
let_it_be(:user) { deploy_token }
context 'when sent by headers' do
let(:headers) { { 'Deploy-Token' => deploy_token.token } }
it_behaves_like 'returning the workhorse send_url response'
end
context 'when sent by basic auth' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
it_behaves_like 'returning the workhorse send_url response'
end
end
context 'with ci job token' do
let_it_be(:job) { create(:ci_build, user: user, status: :running, project: project) }
context 'when sent by headers' do
let(:headers) { { 'Job-Token' => job.token } }
it_behaves_like 'returning the workhorse send_url response'
end
context 'when sent by basic auth' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) }
it_behaves_like 'returning the workhorse send_url response'
end
end
end
context 'with a valid user' do
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let(:headers) { { 'Private-Token' => personal_access_token.token } }
context 'with service response errors' do
@ -1316,8 +1288,6 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
end
end
it_behaves_like 'disabled feature flag'
context 'with a web browser' do
described_class::MAJOR_BROWSERS.each do |browser|
context "when accessing with a #{browser} browser" do
@ -1337,9 +1307,134 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
end
end
context 'for a invalid registry id' do
let(:url) { "/virtual_registries/packages/maven/#{non_existing_record_id}/#{path}" }
it_behaves_like 'returning response status', :not_found
end
it_behaves_like 'disabled feature flag'
it_behaves_like 'disabled dependency proxy'
it_behaves_like 'not authenticated user'
end
it_behaves_like 'not authenticated user'
end
describe 'POST /api/v4/virtual_registries/packages/maven/:id/*path/upload/authorize' do
include_context 'workhorse headers'
let(:path) { 'com/test/package/1.2.3/package-1.2.3.pom' }
let(:url) { "/virtual_registries/packages/maven/#{registry.id}/#{path}/upload/authorize" }
subject(:request) do
post api(url), headers: headers
end
shared_examples 'returning the workhorse authorization response' do
it 'authorizes the upload' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response['TempPath']).not_to be_nil
end
end
it_behaves_like 'authenticated endpoint',
success_shared_example_name: 'returning the workhorse authorization response' do
let(:headers) { workhorse_headers }
end
context 'with a valid user' do
let(:headers) { workhorse_headers.merge(token_header(:personal_access_token)) }
context 'with no workhorse headers' do
let(:headers) { token_header(:personal_access_token) }
it_behaves_like 'returning response status', :forbidden
end
context 'with no permissions on registry' do
let_it_be(:user) { create(:user) }
it_behaves_like 'returning response status', :forbidden
end
it_behaves_like 'disabled feature flag'
it_behaves_like 'disabled dependency proxy'
end
it_behaves_like 'not authenticated user'
end
describe 'POST /api/v4/virtual_registries/packages/maven/:id/*path/upload' do
include_context 'workhorse headers'
let(:path) { 'com/test/package/1.2.3/package-1.2.3.pom' }
let(:url) { "/virtual_registries/packages/maven/#{registry.id}/#{path}/upload" }
let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.pom') }
let(:gid_header) { { described_class::UPSTREAM_GID_HEADER => upstream.to_global_id.to_s } }
let(:headers) { workhorse_headers.merge(gid_header) }
subject(:request) do
workhorse_finalize(
api(url),
file_key: :file,
headers: headers,
params: { file: file_upload },
send_rewritten_field: true
)
end
shared_examples 'returning successful response' do
it 'accepts the upload', :freeze_time do
expect { request }.to change { upstream.cached_responses.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
expect(upstream.cached_responses.last).to have_attributes(
relative_path: "/#{path}",
downloads_count: 1,
upstream_etag: nil,
upstream_checked_at: Time.zone.now,
downloaded_at: Time.zone.now
)
end
end
it_behaves_like 'authenticated endpoint', success_shared_example_name: 'returning successful response'
context 'with a valid user' do
let(:headers) { super().merge(token_header(:personal_access_token)) }
context 'with no workhorse headers' do
let(:headers) { token_header(:personal_access_token).merge(gid_header) }
it_behaves_like 'returning response status', :forbidden
end
context 'with no permissions on registry' do
let_it_be(:user) { create(:user) }
it_behaves_like 'returning response status', :forbidden
end
context 'with an invalid upstream gid' do
let_it_be(:upstream) { build(:virtual_registries_packages_maven_upstream, id: non_existing_record_id) }
it_behaves_like 'returning response status', :not_found
end
context 'with an incoherent upstream gid' do
let_it_be(:upstream) { create(:virtual_registries_packages_maven_upstream) }
it_behaves_like 'returning response status', :not_found
end
it_behaves_like 'disabled feature flag'
it_behaves_like 'disabled dependency proxy'
end
it_behaves_like 'not authenticated user'
end
def token_header(token)

View File

@ -43,14 +43,6 @@ RSpec.describe Ci::StuckBuilds::DropRunningService, feature_category: :continuou
include_examples 'running builds'
context 'when new query flag is disabled' do
before do
stub_feature_flags(ci_new_query_for_running_stuck_jobs: false)
end
include_examples 'running builds'
end
%w[success skipped failed canceled scheduled pending].each do |status|
context "when job is #{status}" do
let(:status) { status }

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe VirtualRegistries::Packages::Maven::CachedResponses::CreateService, :aggregate_failures, feature_category: :virtual_registry do
let_it_be(:registry) { create(:virtual_registries_packages_maven_registry) }
let_it_be(:project) { create(:project, namespace: registry.group) }
let_it_be(:user) { create(:user, owner_of: project) }
let_it_be(:upstream) { create(:virtual_registries_packages_maven_upstream, registry: registry) }
let(:path) { 'com/test/package/1.2.3/package-1.2.3.pom' }
let(:etag) { 'test' }
let(:file) { UploadedFile.new(Tempfile.new(etag).path) }
let(:service) do
described_class.new(upstream: upstream, current_user: user, params: { path: path, file: file, etag: etag })
end
describe '#execute' do
subject(:execute) { service.execute }
shared_examples 'returning a service response success response' do
it 'returns a success service response', :freeze_time do
expect { execute }.to change { upstream.cached_responses.count }.by(1)
expect(execute).to be_success
last_cached_response = upstream.cached_responses.last
expect(execute.payload).to eq(cached_response: last_cached_response)
expect(last_cached_response).to have_attributes(
group_id: registry.group.id,
upstream_checked_at: Time.zone.now,
downloaded_at: Time.zone.now,
downloads_count: 1,
relative_path: "/#{path}",
upstream_etag: etag
)
end
end
context 'with a User' do
it_behaves_like 'returning a service response success response'
end
context 'with a DeployToken' do
let_it_be(:user) { create(:deploy_token, :group, groups: [registry.group], read_virtual_registry: true) }
it_behaves_like 'returning a service response success response'
end
context 'with no path' do
let(:path) { nil }
it { is_expected.to eq(described_class::ERRORS[:path_not_present]) }
end
context 'with no file' do
let(:file) { nil }
it { is_expected.to eq(described_class::ERRORS[:file_not_present]) }
end
context 'with no upstream' do
let_it_be(:upstream) { nil }
it { is_expected.to eq(described_class::ERRORS[:unauthorized]) }
end
context 'with no user' do
let(:user) { nil }
it { is_expected.to eq(described_class::ERRORS[:unauthorized]) }
end
end
end

Some files were not shown because too many files have changed in this diff Show More