Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c01e12a62e
commit
f19ea5c8fa
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
fb51af45d021201dc8fca3b0aeacc80c78527307
|
||||
46b9580af93104de9b4c1d3dda81e9aaf7eb4c01
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,10 +235,6 @@
|
|||
.bash {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.build-log-rounded {
|
||||
border-radius: $gl-border-radius-base;
|
||||
}
|
||||
}
|
||||
|
||||
// Used in EE for Web Terminal
|
||||
|
|
|
|||
|
|
@ -80,10 +80,6 @@
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
&.section-align-top {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.table-button-footer {
|
||||
|
|
|
|||
|
|
@ -202,11 +202,6 @@
|
|||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-full {
|
||||
flex: 1 1 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,5 @@
|
|||
|
||||
.tab-pane {
|
||||
padding: $gl-padding;
|
||||
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
.system-note-message {
|
||||
a {
|
||||
@apply gl-text-blue-500;
|
||||
@apply gl-text-link;
|
||||
}
|
||||
|
||||
.gfm-project_member {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module VirtualRegistries
|
||||
module Packages
|
||||
module Maven
|
||||
class UpstreamPolicy < ::BasePolicy
|
||||
delegate { @subject.registry }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ module VirtualRegistries
|
|||
|
||||
before :cache, :set_content_type
|
||||
|
||||
delegate :filename, to: :model
|
||||
|
||||
def store_dir
|
||||
dynamic_segment
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
a5ea1dd3f424f12824e787b108a476f63d692f84baf1e432751c9a0b5e63b3df
|
||||
|
|
@ -0,0 +1 @@
|
|||
deb023ca7e2268ad69d5e88c6ac6d9a54ef0691cd6e4dbce2ec529e5b5c27b28
|
||||
|
|
@ -0,0 +1 @@
|
|||
19119a07f637d49eb954d67bf80543244a6330573e1e8e450c1cdb1ebd56f523
|
||||
|
|
@ -0,0 +1 @@
|
|||
2b5c7923653143810c7f5e88a62d08a0dab1f335b5bee49e36378990a0446f13
|
||||
|
|
@ -0,0 +1 @@
|
|||
fc71d9f85d2709dda6645527c221700304a8feebeb40cc89b9c836fabd1ba00b
|
||||
|
|
@ -0,0 +1 @@
|
|||
13407f6d933ff98a4996baac347f2ecb1794ead71a3d002a9a57f247ac0290a8
|
||||
|
|
@ -0,0 +1 @@
|
|||
0fa026a081dfe21b0e37ace16fcd77621cab43445240892c8a11ebc918a0475c
|
||||
|
|
@ -0,0 +1 @@
|
|||
cddbd6bf25fdc9e6e82fe1db2f6434c4b21635fa3c60559e71c6e6ad357a82e5
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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` |
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue