Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/agents/review-apps/config.yaml1
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_form.vue2
-rw-r--r--app/assets/javascripts/confidential_merge_request/components/dropdown.vue2
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue2
-rw-r--r--app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue4
-rw-r--r--app/assets/javascripts/diffs/components/compare_dropdown_layout.vue2
-rw-r--r--app/assets/javascripts/ide/components/editor_mode_dropdown.vue2
-rw-r--r--app/assets/javascripts/milestones/components/milestone_combobox.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_header.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboards_dropdown.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/refresh_button.vue4
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue4
-rw-r--r--app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue2
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_template_dropdown.vue2
-rw-r--r--app/assets/javascripts/search/topbar/components/searchable_dropdown.vue4
-rw-r--r--app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/incidents/escalation_status.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/actions_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/registry/persisted_dropdown_selection.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/split_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/timezone_dropdown.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_select/user_select.vue2
-rw-r--r--doc/api/projects.md13
-rw-r--r--doc/ci/pipelines/job_artifacts.md9
-rw-r--r--doc/development/backend/create_source_code_be/index.md3
-rw-r--r--lib/api/helpers/projects_helpers.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--spec/requests/api/projects_spec.rb89
-rw-r--r--workhorse/internal/upstream/routes.go32
-rw-r--r--workhorse/upload_test.go4
39 files changed, 167 insertions, 68 deletions
diff --git a/.gitlab/agents/review-apps/config.yaml b/.gitlab/agents/review-apps/config.yaml
new file mode 100644
index 00000000000..945b3a18d30
--- /dev/null
+++ b/.gitlab/agents/review-apps/config.yaml
@@ -0,0 +1 @@
+# This empty file is used for agent-based integration with Kubernetes
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_form.vue
index 696e7f359d1..388d925196b 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_form.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_form.vue
@@ -109,7 +109,7 @@ export default {
v-for="template in templates"
:key="template.key"
data-qa-selector="incident_templates_item"
- :is-check-item="true"
+ is-check-item
:is-checked="isTemplateSelected(template.key)"
@click="selectIssueTemplate(template.key)"
>
diff --git a/app/assets/javascripts/confidential_merge_request/components/dropdown.vue b/app/assets/javascripts/confidential_merge_request/components/dropdown.vue
index 6bb654a434f..9cb7cd9607f 100644
--- a/app/assets/javascripts/confidential_merge_request/components/dropdown.vue
+++ b/app/assets/javascripts/confidential_merge_request/components/dropdown.vue
@@ -40,7 +40,7 @@ export default {
<gl-dropdown-item
v-for="project in projects"
:key="project.id"
- :is-check-item="true"
+ is-check-item
:is-checked="project.id === selectedProject.id"
@click="selectProject(project)"
>
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue
index 5e3bf5350ba..a9668ebdb69 100644
--- a/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/code_block_bubble_menu.vue
@@ -182,7 +182,7 @@ export default {
</template>
<template v-if="!showCustomLanguageInput" #highlighted-items>
- <gl-dropdown-item :key="selectedLanguage.syntax" is-check-item :is-checked="true">
+ <gl-dropdown-item :key="selectedLanguage.syntax" is-check-item is-checked>
{{ selectedLanguage.label }}
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue b/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue
index 816d7ac7abf..f10545faea6 100644
--- a/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue
+++ b/app/assets/javascripts/design_management/components/upload/design_version_dropdown.vue
@@ -73,8 +73,8 @@ export default {
<gl-dropdown-item
v-for="(version, index) in allVersions"
:key="version.id"
- :is-check-item="true"
- :is-check-centered="true"
+ is-check-item
+ is-check-centered
:is-checked="findVersionId(version.id) === currentVersionId"
:avatar-url="getAvatarUrl(version)"
@click="routeToVersion(version.id)"
diff --git a/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue b/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue
index fd219a7d00f..4501988ee4f 100644
--- a/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue
+++ b/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue
@@ -37,7 +37,7 @@ export default {
:class="{
'is-active': version.selected,
}"
- :is-check-item="true"
+ is-check-item
:is-checked="version.selected"
:href="version.href"
>
diff --git a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
index 52593aabfea..d40aab8ee4f 100644
--- a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
+++ b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
@@ -50,7 +50,7 @@ export default {
<gl-dropdown-item
v-for="mode in modeDropdownItems"
:key="mode.viewerType"
- :is-check-item="true"
+ is-check-item
:is-checked="viewer === mode.viewerType"
@click="changeMode(mode.viewerType)"
>
diff --git a/app/assets/javascripts/milestones/components/milestone_combobox.vue b/app/assets/javascripts/milestones/components/milestone_combobox.vue
index 59d2a2b29b3..5c3b969655b 100644
--- a/app/assets/javascripts/milestones/components/milestone_combobox.vue
+++ b/app/assets/javascripts/milestones/components/milestone_combobox.vue
@@ -243,7 +243,7 @@ export default {
v-for="(item, idx) in extraLinks"
:key="idx"
:href="item.url"
- :is-check-item="true"
+ is-check-item
data-testid="milestone-combobox-extra-links"
>
{{ item.text }}
diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue
index 3338635bf96..8956fe166e8 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_header.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue
@@ -202,7 +202,7 @@ export default {
<gl-dropdown-item
v-for="environment in filteredEnvironments"
:key="environment.id"
- :is-check-item="true"
+ is-check-item
:is-checked="environment.name === currentEnvironmentName"
:href="getEnvironmentPath(environment.id)"
>
diff --git a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
index 568c66cf152..7fae684315c 100644
--- a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
+++ b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
@@ -86,7 +86,7 @@ export default {
<gl-dropdown-item
v-for="dashboard in starredDashboards"
:key="dashboard.path"
- :is-check-item="true"
+ is-check-item
:is-checked="dashboard.path === selectedDashboardPath"
@click="selectDashboard(dashboard)"
>
@@ -105,7 +105,7 @@ export default {
<gl-dropdown-item
v-for="dashboard in nonStarredDashboards"
:key="dashboard.path"
- :is-check-item="true"
+ is-check-item
:is-checked="dashboard.path === selectedDashboardPath"
@click="selectDashboard(dashboard)"
>
diff --git a/app/assets/javascripts/monitoring/components/refresh_button.vue b/app/assets/javascripts/monitoring/components/refresh_button.vue
index 0b80043a92c..544fe10f26e 100644
--- a/app/assets/javascripts/monitoring/components/refresh_button.vue
+++ b/app/assets/javascripts/monitoring/components/refresh_button.vue
@@ -163,7 +163,7 @@ export default {
:text="dropdownText"
>
<gl-dropdown-item
- :is-check-item="true"
+ is-check-item
:is-checked="refreshInterval === null"
@click="removeRefreshInterval()"
>{{ __('Off') }}</gl-dropdown-item
@@ -172,7 +172,7 @@ export default {
<gl-dropdown-item
v-for="(option, i) in $options.refreshIntervals"
:key="i"
- :is-check-item="true"
+ is-check-item
:is-checked="isChecked(option)"
@click="setRefreshInterval(option)"
>{{ option.label }}</gl-dropdown-item
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 10e651033e8..8a42fb6bd85 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -177,7 +177,7 @@ export default {
v-for="{ text, key, cls } in $options.SORT_OPTIONS"
:key="text"
:class="cls"
- :is-check-item="true"
+ is-check-item
:is-checked="isSortDropdownItemActive(key)"
@click="fetchSortedDiscussions(key)"
>
@@ -192,7 +192,7 @@ export default {
<gl-dropdown-item
v-for="filter in filters"
:key="filter.value"
- :is-check-item="true"
+ is-check-item
:is-checked="filter.value === currentValue"
:class="{ 'is-active': filter.value === currentValue }"
:data-filter-type="filterType(filter.value)"
diff --git a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
index d7e68484143..08d24344ffc 100644
--- a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
+++ b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
@@ -180,7 +180,7 @@ export default {
v-for="({ group_name }, index) in dailyCoverageData"
:key="index"
:value="group_name"
- :is-check-item="true"
+ is-check-item
:is-checked="index === selectedCoverageIndex"
@click="setSelectedCoverage(index)"
>
diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
index 4398ba67d47..1f8ddae3696 100644
--- a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
+++ b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
@@ -237,7 +237,7 @@ export default {
v-for="branch in availableBranches"
:key="branch"
:is-checked="currentBranch === branch"
- :is-check-item="true"
+ is-check-item
data-qa-selector="branch_menu_item_button"
@click="selectBranch(branch)"
>
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_template_dropdown.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_template_dropdown.vue
index bdd9f940d79..315f0743b53 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_template_dropdown.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_template_dropdown.vue
@@ -100,7 +100,7 @@ export default {
<gl-dropdown-item
v-for="template in item"
:key="template.key"
- :is-check-item="true"
+ is-check-item
:is-checked="
template.project_id === selectedFileTemplateProjectId &&
template.name === selectedTemplate
diff --git a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue b/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
index 5653cddda60..ff639d538b3 100644
--- a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
+++ b/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue
@@ -144,9 +144,9 @@ export default {
/>
<gl-dropdown-item
class="gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2"
- :is-check-item="true"
+ is-check-item
:is-checked="isSelected($options.ANY_OPTION)"
- :is-check-centered="true"
+ is-check-centered
@click="updateDropdown($options.ANY_OPTION)"
>
<span data-testid="item-title">{{ $options.ANY_OPTION.name }}</span>
diff --git a/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue b/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
index a4254a355a2..70156142365 100644
--- a/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
+++ b/app/assets/javascripts/search/topbar/components/searchable_dropdown_item.vue
@@ -53,9 +53,9 @@ export default {
<template>
<gl-dropdown-item
- :is-check-item="true"
+ is-check-item
:is-checked="isSelected"
- :is-check-centered="true"
+ is-check-centered
@click="$emit('change', item)"
>
<div class="gl-display-flex gl-align-items-center">
diff --git a/app/assets/javascripts/sidebar/components/incidents/escalation_status.vue b/app/assets/javascripts/sidebar/components/incidents/escalation_status.vue
index aeaac76cff4..9c41db98c63 100644
--- a/app/assets/javascripts/sidebar/components/incidents/escalation_status.vue
+++ b/app/assets/javascripts/sidebar/components/incidents/escalation_status.vue
@@ -62,7 +62,7 @@ export default {
v-for="status in $options.STATUS_LIST"
:key="status"
data-testid="status-dropdown-item"
- :is-check-item="true"
+ is-check-item
:is-checked="status === value"
@click="$emit('input', status)"
>
diff --git a/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue
index bf4ba715f85..a562df4ecd6 100644
--- a/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue
+++ b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue
@@ -179,7 +179,7 @@ export default {
v-for="option in severitiesList"
:key="option.value"
data-testid="severityDropdownItem"
- :is-check-item="true"
+ is-check-item
:is-checked="option.value === severity"
@click="updateSeverity(option.value)"
>
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index e50d4ae79c8..b62a2c7bcd1 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -369,7 +369,7 @@ export default {
<gl-search-box-by-type ref="search" v-model="searchTerm" />
<gl-dropdown-item
:data-testid="`no-${formatIssuableAttribute.kebab}-item`"
- :is-check-item="true"
+ is-check-item
:is-checked="isAttributeChecked($options.noAttributeId)"
@click="updateAttribute($options.noAttributeId)"
>
@@ -396,7 +396,7 @@ export default {
<gl-dropdown-item
v-for="attrItem in attributesList"
:key="attrItem.id"
- :is-check-item="true"
+ is-check-item
:is-checked="isAttributeChecked(attrItem.id)"
:data-testid="`${formatIssuableAttribute.kebab}-items`"
@click="updateAttribute(attrItem.id)"
diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue
index 6db18afe51c..c6c22f9c61f 100644
--- a/app/assets/javascripts/vue_shared/components/actions_button.vue
+++ b/app/assets/javascripts/vue_shared/components/actions_button.vue
@@ -77,7 +77,7 @@ export default {
<template v-for="(action, index) in actions">
<gl-dropdown-item
:key="action.key"
- :is-check-item="true"
+ is-check-item
:is-checked="action.key === selectedAction.key"
:secondary-text="action.secondaryText"
:data-qa-selector="`${action.key}_menu_item`"
diff --git a/app/assets/javascripts/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue b/app/assets/javascripts/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue
index 91906388049..22f3c35b9c3 100644
--- a/app/assets/javascripts/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue
+++ b/app/assets/javascripts/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue
@@ -42,8 +42,8 @@ export default {
v-for="color in colors"
:key="color.color"
:is-checked="isColorSelected(color)"
- :is-check-centered="true"
- :is-check-item="true"
+ is-check-centered
+ is-check-item
@click.native.capture.stop="handleColorClick(color)"
>
<color-item :color="color.color" :title="color.title" />
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
index 840911dc99c..faa50a50c69 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
@@ -149,8 +149,8 @@ export default {
v-for="option in presetOptions"
:key="option.id"
:is-checked="isSelected(option)"
- :is-check-centered="true"
- :is-check-item="true"
+ is-check-centered
+ is-check-item
@click.native.capture.stop="selectOption(option)"
>
<slot name="preset-item" :item="option">
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index 33d507dad57..e311df6e66f 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -369,7 +369,7 @@ export default {
<gl-dropdown-item
v-for="sortBy in sortOptions"
:key="sortBy.id"
- :is-check-item="true"
+ is-check-item
:is-checked="sortBy.id === selectedSortOption.id"
@click="handleSortOptionClick(sortBy)"
>{{ sortBy.title }}</gl-dropdown-item
diff --git a/app/assets/javascripts/vue_shared/components/registry/persisted_dropdown_selection.vue b/app/assets/javascripts/vue_shared/components/registry/persisted_dropdown_selection.vue
index 43a8e241d77..32d7cdad568 100644
--- a/app/assets/javascripts/vue_shared/components/registry/persisted_dropdown_selection.vue
+++ b/app/assets/javascripts/vue_shared/components/registry/persisted_dropdown_selection.vue
@@ -49,7 +49,7 @@ export default {
v-for="option in parsedOptions"
:key="option.value"
:is-checked="option.selected"
- :is-check-item="true"
+ is-check-item
@click="setSelected(option.value)"
>
{{ option.label }}
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
index bfaf3b92c34..c5d3704ead9 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
@@ -253,7 +253,7 @@ export default {
<gl-dropdown-item
v-for="architecture in architectures"
:key="architecture.name"
- :is-check-item="true"
+ is-check-item
:is-checked="selectedArchitecture === architecture.name"
data-testid="architecture-dropdown-item"
@click="selectArchitecture(architecture.name)"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
index dfa2ca2d20c..0f5560ff628 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
@@ -180,7 +180,7 @@ export default {
<gl-dropdown-item
v-for="project in projects"
:key="project.id"
- :is-check-item="true"
+ is-check-item
:is-checked="isSelectedProject(project)"
@click.stop.prevent="handleProjectSelect(project)"
>{{ project.name_with_namespace }}</gl-dropdown-item
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
index f595e635f2c..8d3d4d5f86a 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
@@ -154,8 +154,8 @@ export default {
v-for="(label, index) in visibleLabels"
:key="label.id"
:is-checked="isLabelSelected(label)"
- :is-check-centered="true"
- :is-check-item="true"
+ is-check-centered
+ is-check-item
:active="shouldHighlightFirstItem && index === 0"
active-class="is-focused"
data-testid="labels-list"
diff --git a/app/assets/javascripts/vue_shared/components/split_button.vue b/app/assets/javascripts/vue_shared/components/split_button.vue
index 994fa68fb1a..c0aef42b0f2 100644
--- a/app/assets/javascripts/vue_shared/components/split_button.vue
+++ b/app/assets/javascripts/vue_shared/components/split_button.vue
@@ -68,7 +68,7 @@ export default {
<template v-for="(item, itemIndex) in actionItems">
<gl-dropdown-item
:key="item.eventName"
- :is-check-item="true"
+ is-check-item
:is-checked="selectedItem === item"
@click="changeSelectedItem(item)"
>
diff --git a/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue b/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
index 42334d80eec..ce65266cbc9 100644
--- a/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
@@ -72,7 +72,7 @@ export default {
v-for="timezone in filteredResults"
:key="timezone.formattedTimezone"
:is-checked="isSelected(timezone)"
- :is-check-item="true"
+ is-check-item
@click="selectTimezone(timezone)"
>
{{ timezone.formattedTimezone }}
diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
index 43a590c2367..3180bd0d283 100644
--- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
+++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
@@ -320,7 +320,7 @@ export default {
<gl-dropdown-item
v-if="isSearchEmpty"
:is-checked="selectedIsEmpty"
- :is-check-centered="true"
+ is-check-centered
data-testid="unassign"
@click.native.capture.stop="$emit('input', [])"
>
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 32b26f32726..56fa36a44f8 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -2248,6 +2248,19 @@ Returned object:
}
```
+## Remove a project avatar
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92604) in GitLab 15.4.
+
+To remove a project avatar, use a blank value for the `avatar` attribute.
+
+Example request:
+
+```shell
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
+ --data "avatar=" "https://gitlab.example.com/api/v4/projects/5"
+```
+
## Share project with group
Allow to share project with group.
diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md
index c8babe3320d..6ca42479e8e 100644
--- a/doc/ci/pipelines/job_artifacts.md
+++ b/doc/ci/pipelines/job_artifacts.md
@@ -451,3 +451,12 @@ test-job:
reports:
dotenv: build.env
```
+
+### Job artifacts are not expired
+
+If some job artifacts are not expiring as expected, check if the
+[**Keep artifacts from most recent successful jobs**](#keep-artifacts-from-most-recent-successful-jobs)
+setting is enabled.
+
+When this setting is enabled, job artifacts from the latest successful pipeline
+of each ref do not expire and are not deleted.
diff --git a/doc/development/backend/create_source_code_be/index.md b/doc/development/backend/create_source_code_be/index.md
index e1ee78731de..a1322b3fa25 100644
--- a/doc/development/backend/create_source_code_be/index.md
+++ b/doc/development/backend/create_source_code_be/index.md
@@ -33,6 +33,9 @@ GitLab Shell handles Git SSH sessions for GitLab and modifies the list of author
For more information, [refer to the README](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/README.md).
for GitLab Shell.
+To learn about the reasoning behind our creation of `gitlab-sshd`, read the blog post
+[Why we implemented our own SSHD solution](https://about.gitlab.com/blog/2022/08/17/why-we-have-implemented-our-own-sshd-solution-on-gitlab-sass/).
+
## GitLab Rails
### Gitaly touch points
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 628182ad1ab..2d0e8ae4bfd 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -58,7 +58,7 @@ module API
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Deprecated: Use :topics instead'
optional :topics, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of topics for a project'
# TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
- optional :avatar, type: File, desc: 'Avatar image for project' # rubocop:disable Scalability/FileUploads
+ optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for project'
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
optional :suggestion_commit_message, type: String, desc: 'The commit message used to apply merge request suggestions'
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index f8dc3d45a5c..8c58cc585d8 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -453,6 +453,8 @@ module API
filter_attributes_using_license!(attrs)
verify_update_project_attrs!(user_project, attrs)
+ user_project.remove_avatar! if attrs.key?(:avatar) && attrs[:avatar].nil?
+
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
if result[:status] == :success
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fcbd0cc784b..ac534b4e711 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -48,6 +48,7 @@ end
RSpec.describe API::Projects do
include ProjectForksHelper
+ include WorkhorseHelpers
include StubRequests
let_it_be(:user) { create(:user) }
@@ -1349,7 +1350,12 @@ RSpec.describe API::Projects do
it 'uploads avatar for project a project' do
project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
- post api('/projects', user), params: project
+ workhorse_form_with_file(
+ api('/projects', user),
+ method: :post,
+ file_key: :avatar,
+ params: project
+ )
project_id = json_response['id']
expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif")
@@ -1925,8 +1931,6 @@ RSpec.describe API::Projects do
end
describe "POST /projects/:id/uploads/authorize" do
- include WorkhorseHelpers
-
let(:headers) { workhorse_internal_api_request_header.merge({ 'HTTP_GITLAB_WORKHORSE' => 1 }) }
context 'with authorized user' do
@@ -3584,18 +3588,77 @@ RSpec.describe API::Projects do
end
end
- it 'updates avatar' do
- project_param = {
- avatar: fixture_file_upload('spec/fixtures/banana_sample.gif',
- 'image/gif')
- }
+ context 'with changes to the avatar' do
+ let_it_be(:avatar_file) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
+ let_it_be(:alternate_avatar_file) { fixture_file_upload('spec/fixtures/rails_sample.png', 'image/png') }
+ let_it_be(:project_with_avatar, reload: true) do
+ create(:project,
+ :private,
+ :repository,
+ name: 'project-with-avatar',
+ creator_id: user.id,
+ namespace: user.namespace,
+ avatar: avatar_file)
+ end
- put api("/projects/#{project3.id}", user), params: project_param
+ it 'uploads avatar to project without an avatar' do
+ workhorse_form_with_file(
+ api("/projects/#{project3.id}", user),
+ method: :put,
+ file_key: :avatar,
+ params: { avatar: avatar_file }
+ )
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
- '-/system/project/avatar/'\
- "#{project3.id}/banana_sample.gif")
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
+ '-/system/project/avatar/'\
+ "#{project3.id}/banana_sample.gif")
+ end
+ end
+
+ it 'uploads and changes avatar to project with an avatar' do
+ workhorse_form_with_file(
+ api("/projects/#{project_with_avatar.id}", user),
+ method: :put,
+ file_key: :avatar,
+ params: { avatar: alternate_avatar_file }
+ )
+
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
+ '-/system/project/avatar/'\
+ "#{project_with_avatar.id}/rails_sample.png")
+ end
+ end
+
+ it 'uploads and changes avatar to project among other changes' do
+ workhorse_form_with_file(
+ api("/projects/#{project_with_avatar.id}", user),
+ method: :put,
+ file_key: :avatar,
+ params: { description: 'changed description', avatar: avatar_file }
+ )
+
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['description']).to eq('changed description')
+ expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
+ '-/system/project/avatar/'\
+ "#{project_with_avatar.id}/banana_sample.gif")
+ end
+ end
+
+ it 'removes avatar from project with an avatar' do
+ put api("/projects/#{project_with_avatar.id}", user), params: { avatar: '' }
+
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['avatar_url']).to be_nil
+ expect(project_with_avatar.reload.avatar_url).to be_nil
+ end
+ end
end
it 'updates auto_devops_deploy_strategy' do
diff --git a/workhorse/internal/upstream/routes.go b/workhorse/internal/upstream/routes.go
index 311028750a5..0bd86cfe039 100644
--- a/workhorse/internal/upstream/routes.go
+++ b/workhorse/internal/upstream/routes.go
@@ -51,7 +51,7 @@ const (
gitProjectPattern = `^/.+\.git/`
geoGitProjectPattern = `^/[^-].+\.git/` // Prevent matching routes like /-/push_from_secondary
projectPattern = `^/([^/]+/){1,}[^/]+/`
- apiProjectPattern = apiPattern + `v4/projects/[^/]+/` // API: Projects can be encoded via group%2Fsubgroup%2Fproject
+ apiProjectPattern = apiPattern + `v4/projects/[^/]+` // API: Projects can be encoded via group%2Fsubgroup%2Fproject
apiTopicPattern = apiPattern + `v4/topics`
snippetUploadPattern = `^/uploads/personal_snippet`
userUploadPattern = `^/uploads/user`
@@ -269,40 +269,40 @@ func configureRoutes(u *upstream) {
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56731.
// Maven Artifact Repository
- u.route("PUT", apiProjectPattern+`packages/maven/`, requestBodyUploader),
+ u.route("PUT", apiProjectPattern+`/packages/maven/`, requestBodyUploader),
// Conan Artifact Repository
u.route("PUT", apiPattern+`v4/packages/conan/`, requestBodyUploader),
- u.route("PUT", apiProjectPattern+`packages/conan/`, requestBodyUploader),
+ u.route("PUT", apiProjectPattern+`/packages/conan/`, requestBodyUploader),
// Generic Packages Repository
- u.route("PUT", apiProjectPattern+`packages/generic/`, requestBodyUploader),
+ u.route("PUT", apiProjectPattern+`/packages/generic/`, requestBodyUploader),
// NuGet Artifact Repository
- u.route("PUT", apiProjectPattern+`packages/nuget/`, mimeMultipartUploader),
+ u.route("PUT", apiProjectPattern+`/packages/nuget/`, mimeMultipartUploader),
// PyPI Artifact Repository
- u.route("POST", apiProjectPattern+`packages/pypi`, mimeMultipartUploader),
+ u.route("POST", apiProjectPattern+`/packages/pypi`, mimeMultipartUploader),
// Debian Artifact Repository
- u.route("PUT", apiProjectPattern+`packages/debian/`, requestBodyUploader),
+ u.route("PUT", apiProjectPattern+`/packages/debian/`, requestBodyUploader),
// RPM Artifact Repository
u.route("POST", apiProjectPattern+`packages/rpm/`, requestBodyUploader),
// Gem Artifact Repository
- u.route("POST", apiProjectPattern+`packages/rubygems/`, requestBodyUploader),
+ u.route("POST", apiProjectPattern+`/packages/rubygems/`, requestBodyUploader),
// Terraform Module Package Repository
- u.route("PUT", apiProjectPattern+`packages/terraform/modules/`, requestBodyUploader),
+ u.route("PUT", apiProjectPattern+`/packages/terraform/modules/`, requestBodyUploader),
// Helm Artifact Repository
- u.route("POST", apiProjectPattern+`packages/helm/api/[^/]+/charts\z`, mimeMultipartUploader),
+ u.route("POST", apiProjectPattern+`/packages/helm/api/[^/]+/charts\z`, mimeMultipartUploader),
// We are porting API to disk acceleration
// we need to declare each routes until we have fixed all the routes on the rails codebase.
// Overall status can be seen at https://gitlab.com/groups/gitlab-org/-/epics/1802#current-status
- u.route("POST", apiProjectPattern+`wikis/attachments\z`, tempfileMultipartProxy),
+ u.route("POST", apiProjectPattern+`/wikis/attachments\z`, tempfileMultipartProxy),
u.route("POST", apiPattern+`graphql\z`, tempfileMultipartProxy),
u.route("POST", apiTopicPattern, tempfileMultipartProxy),
u.route("PUT", apiTopicPattern, tempfileMultipartProxy),
@@ -315,16 +315,20 @@ func configureRoutes(u *upstream) {
u.route("POST", importPattern+`gitlab_group`, mimeMultipartUploader),
// Issuable Metric image upload
- u.route("POST", apiProjectPattern+`issues/[0-9]+/metric_images\z`, mimeMultipartUploader),
+ u.route("POST", apiProjectPattern+`/issues/[0-9]+/metric_images\z`, mimeMultipartUploader),
// Alert Metric image upload
- u.route("POST", apiProjectPattern+`alert_management_alerts/[0-9]+/metric_images\z`, mimeMultipartUploader),
+ u.route("POST", apiProjectPattern+`/alert_management_alerts/[0-9]+/metric_images\z`, mimeMultipartUploader),
// Requirements Import via UI upload acceleration
u.route("POST", projectPattern+`requirements_management/requirements/import_csv`, mimeMultipartUploader),
// Uploads via API
- u.route("POST", apiProjectPattern+`uploads\z`, mimeMultipartUploader),
+ u.route("POST", apiProjectPattern+`/uploads\z`, mimeMultipartUploader),
+
+ // Project Avatar
+ u.route("POST", apiPattern+`v4/projects\z`, tempfileMultipartProxy),
+ u.route("PUT", apiProjectPattern+`\z`, tempfileMultipartProxy),
// Explicitly proxy API requests
u.route("", apiPattern, proxy),
diff --git a/workhorse/upload_test.go b/workhorse/upload_test.go
index 2d7806b1790..fd4844220b9 100644
--- a/workhorse/upload_test.go
+++ b/workhorse/upload_test.go
@@ -122,6 +122,10 @@ func TestAcceleratedUpload(t *testing.T) {
{"POST", `/example`, false},
{"POST", `/uploads/personal_snippet`, true},
{"POST", `/uploads/user`, true},
+ {"POST", `/api/v4/projects`, false},
+ {"PUT", `/api/v4/projects/group%2Fproject`, false},
+ {"PUT", `/api/v4/projects/group%2Fsubgroup%2Fproject`, false},
+ {"PUT", `/api/v4/projects/39`, false},
{"POST", `/api/v4/projects/1/uploads`, true},
{"POST", `/api/v4/projects/group%2Fproject/uploads`, true},
{"POST", `/api/v4/projects/group%2Fsubgroup%2Fproject/uploads`, true},