diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 193cf7ca6f8..27a907dec8a 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-6bd8110489a44adeacff1897a757d8ea52bf4f87
+ee1bf60a8952f6e9b4f3bf7627ac671ef1427cad
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index b1a8b7b864e..648c04b679f 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-14.40.0
+14.41.0
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index c2aea4a7074..82f5eb829bd 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -4,6 +4,7 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { debounce, throttle } from 'lodash';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapGetters, mapActions } from 'vuex';
+import { mapState as mapPiniaState, mapActions as mapPiniaActions } from 'pinia';
import FindingsDrawer from 'ee_component/diffs/components/shared/findings_drawer.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { convertToGraphQLId } from '~/graphql_shared/utils';
@@ -18,7 +19,7 @@ import {
import { createAlert } from '~/alert';
import { InternalEvents } from '~/tracking';
import { helpPagePath } from '~/helpers/help_page_helper';
-import { parseBoolean, handleLocationHash } from '~/lib/utils/common_utils';
+import { parseBoolean, handleLocationHash, getCookie } from '~/lib/utils/common_utils';
import { BV_HIDE_TOOLTIP, DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { Mousetrap } from '~/lib/mousetrap';
import { updateHistory, getLocationHash } from '~/lib/utils/url_utility';
@@ -27,9 +28,9 @@ import { __ } from '~/locale';
import notesEventHub from '~/notes/event_hub';
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
import getMRCodequalityAndSecurityReports from 'ee_else_ce/diffs/components/graphql/get_mr_codequality_and_security_reports.query.graphql';
+import { useFileBrowser } from '~/diffs/stores/file_browser';
import { sortFindingsByFile } from '../utils/sort_findings_by_file';
import {
- MR_TREE_SHOW_KEY,
ALERT_OVERFLOW_HIDDEN,
ALERT_MERGE_CONFLICT,
ALERT_COLLAPSED_FILES,
@@ -44,6 +45,7 @@ import {
TRACKING_MULTIPLE_FILES_MODE,
EVT_MR_PREPARED,
EVT_DISCUSSIONS_ASSIGNED,
+ FILE_BROWSER_VISIBLE,
} from '../constants';
import { isCollapsed } from '../utils/diff_file';
@@ -243,7 +245,6 @@ export default {
'showWhitespace',
'targetBranchName',
'branchName',
- 'showTreeList',
'addedLines',
'removedLines',
]),
@@ -259,6 +260,7 @@ export default {
]),
...mapGetters(['isNotesFetched', 'getNoteableData']),
...mapGetters('findingsDrawer', ['activeDrawer']),
+ ...mapPiniaState(useFileBrowser, ['fileBrowserVisible']),
diffs() {
if (!this.viewDiffsFileByFile) {
return this.diffFiles;
@@ -316,7 +318,7 @@ export default {
return convertToGraphQLId('MergeRequest', this.getNoteableData.id);
},
renderFileTree() {
- return this.renderDiffFiles && this.showTreeList;
+ return this.renderDiffFiles && this.fileBrowserVisible;
},
hideTooltips() {
const hide = () => {
@@ -455,12 +457,10 @@ export default {
'setCurrentFileHash',
'setHighlightedRow',
'goToFile',
- 'setShowTreeList',
'navigateToDiffFileIndex',
'setFileByFile',
'disableVirtualScroller',
'fetchLinkedFile',
- 'toggleTreeList',
'expandAllFiles',
'collapseAllFiles',
'setDiffViewType',
@@ -468,6 +468,7 @@ export default {
'goToFile',
]),
...mapActions('findingsDrawer', ['setDrawer']),
+ ...mapPiniaActions(useFileBrowser, ['setFileBrowserVisibility', 'toggleFileBrowserVisibility']),
closeDrawer() {
this.setDrawer({});
},
@@ -684,16 +685,14 @@ export default {
}
},
setTreeDisplay() {
- const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
- let showTreeList = true;
-
- if (storedTreeShow !== null) {
- showTreeList = parseBoolean(storedTreeShow);
- } else if (!bp.isDesktop() || (!this.isBatchLoading && this.flatBlobsList.length <= 1)) {
- showTreeList = false;
+ const hideBrowserPreference = getCookie(FILE_BROWSER_VISIBLE);
+ if (hideBrowserPreference) {
+ this.setFileBrowserVisibility(parseBoolean(hideBrowserPreference));
+ } else {
+ const shouldHide =
+ !bp.isDesktop() || (!this.isBatchLoading && this.flatBlobsList.length <= 1);
+ this.setFileBrowserVisibility(!shouldHide);
}
-
- return this.setShowTreeList({ showTreeList, saving: false });
},
async scrollVirtualScrollerToFileHash(hash) {
const index = this.diffFiles.findIndex((f) => f.file_hash === hash);
@@ -741,7 +740,7 @@ export default {
}
},
fileTreeToggled() {
- this.toggleTreeList();
+ this.toggleFileBrowserVisibility();
this.adjustView();
},
isDiffViewActive(item) {
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index a0b507e07e2..32a5ac46b0a 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -10,6 +10,7 @@ import {
} from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapActions, mapGetters, mapState } from 'vuex';
+import { mapActions as mapPiniaActions, mapState as mapPiniaState } from 'pinia';
import { __ } from '~/locale';
import { setUrlParams } from '~/lib/utils/url_utility';
import {
@@ -20,6 +21,7 @@ import {
} from '~/behaviors/shortcuts/keybindings';
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
import { sanitize } from '~/lib/dompurify';
+import { useFileBrowser } from '~/diffs/stores/file_browser';
import CompareDropdownLayout from './compare_dropdown_layout.vue';
export default {
@@ -47,12 +49,13 @@ export default {
'diffCompareDropdownTargetVersions',
'diffCompareDropdownSourceVersions',
]),
- ...mapState('diffs', ['commit', 'showTreeList', 'startVersion', 'latestVersionPath']),
+ ...mapState('diffs', ['commit', 'startVersion', 'latestVersionPath']),
+ ...mapPiniaState(useFileBrowser, ['fileBrowserVisible']),
toggleFileBrowserShortcutKey() {
return shouldDisableShortcuts() ? null : keysFor(MR_TOGGLE_FILE_BROWSER)[0];
},
toggleFileBrowserTitle() {
- return this.showTreeList ? __('Hide file browser') : __('Show file browser');
+ return this.fileBrowserVisible ? __('Hide file browser') : __('Show file browser');
},
toggleFileBrowserTooltip() {
const description = this.toggleFileBrowserTitle;
@@ -113,7 +116,7 @@ export default {
},
},
methods: {
- ...mapActions('diffs', ['setShowTreeList']),
+ ...mapPiniaActions(useFileBrowser, ['toggleFileBrowserVisibility']),
...mapActions('diffs', ['moveToNeighboringCommit']),
},
};
@@ -130,10 +133,10 @@ export default {
data-testid="file-tree-button"
:aria-label="toggleFileBrowserTitle"
:aria-keyshortcuts="toggleFileBrowserShortcutKey"
- :selected="showTreeList"
- @click="setShowTreeList({ showTreeList: !showTreeList })"
+ :selected="fileBrowserVisible"
+ @click="toggleFileBrowserVisibility"
>
-
+
{{ __('Viewing commit') }}
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 77ca5f2009a..209b8400226 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -28,7 +28,7 @@ export const LENGTH_OF_AVATAR_TOOLTIP = 17;
export const DIFF_FILE_SYMLINK_MODE = '120000';
export const DIFF_FILE_DELETED_MODE = '0';
-export const MR_TREE_SHOW_KEY = 'mr_tree_show';
+export const FILE_BROWSER_VISIBLE = 'file_browser_visible';
export const TREE_TYPE = 'tree';
export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 0517a146b50..c7086352079 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -29,7 +29,6 @@ import {
import {
INLINE_DIFF_VIEW_TYPE,
DIFF_VIEW_COOKIE_NAME,
- MR_TREE_SHOW_KEY,
TREE_LIST_STORAGE_KEY,
OLD_LINE_KEY,
NEW_LINE_KEY,
@@ -718,18 +717,6 @@ export const scrollToFile = ({ state, commit, getters }, { path }) => {
}
};
-export const setShowTreeList = ({ commit }, { showTreeList, saving = true }) => {
- commit(types.SET_SHOW_TREE_LIST, showTreeList);
-
- if (saving) {
- localStorage.setItem(MR_TREE_SHOW_KEY, showTreeList);
- }
-};
-
-export const toggleTreeList = ({ state, commit }) => {
- commit(types.SET_SHOW_TREE_LIST, !state.showTreeList);
-};
-
export const openDiffFileCommentForm = ({ commit, getters }, formData) => {
const form = getters.getCommentFormForDiffFile(formData.fileHash);
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 763c6084491..2a7efbbb74b 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -25,7 +25,6 @@ export default () => ({
diffViewType: INLINE_DIFF_VIEW_TYPE,
tree: [],
treeEntries: {},
- showTreeList: true,
currentDiffFileId: '',
projectPath: '',
viewedDiffFileIds: {},
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 2621aeaacd6..ec30ddb368e 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -23,7 +23,6 @@ export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FIL
export const TOGGLE_FOLDER_OPEN = 'TOGGLE_FOLDER_OPEN';
export const SET_FOLDER_OPEN = 'SET_FOLDER_OPEN';
export const TREE_ENTRY_DIFF_LOADING = 'TREE_ENTRY_DIFF_LOADING';
-export const SET_SHOW_TREE_LIST = 'SET_SHOW_TREE_LIST';
export const SET_CURRENT_DIFF_FILE = 'SET_CURRENT_DIFF_FILE';
export const SET_DIFF_FILE_VIEWED = 'SET_DIFF_FILE_VIEWED';
export const SET_LINKED_FILE_HASH = 'SET_LINKED_FILE_HASH';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 9f219284375..4a80d3bbb67 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -297,9 +297,6 @@ export default {
[types.TREE_ENTRY_DIFF_LOADING](state, { path, loading = true }) {
state.treeEntries[path].diffLoading = loading;
},
- [types.SET_SHOW_TREE_LIST](state, showTreeList) {
- state.showTreeList = showTreeList;
- },
[types.SET_CURRENT_DIFF_FILE](state, fileId) {
state.currentDiffFileId = fileId;
},
diff --git a/app/assets/javascripts/diffs/stores/file_browser.js b/app/assets/javascripts/diffs/stores/file_browser.js
new file mode 100644
index 00000000000..8c59f8e2614
--- /dev/null
+++ b/app/assets/javascripts/diffs/stores/file_browser.js
@@ -0,0 +1,26 @@
+import { defineStore } from 'pinia';
+import { getCookie, parseBoolean, setCookie } from '~/lib/utils/common_utils';
+import { FILE_BROWSER_VISIBLE } from '~/diffs/constants';
+
+export const useFileBrowser = defineStore('fileBrowser', {
+ state() {
+ return {
+ fileBrowserVisible: true,
+ };
+ },
+ actions: {
+ setFileBrowserVisibility(visible) {
+ this.fileBrowserVisible = visible;
+ },
+ toggleFileBrowserVisibility() {
+ this.fileBrowserVisible = !this.fileBrowserVisible;
+ setCookie(FILE_BROWSER_VISIBLE, this.fileBrowserVisible);
+ },
+ initFileBrowserVisibility() {
+ const visibilityPreference = getCookie(FILE_BROWSER_VISIBLE);
+ if (visibilityPreference) {
+ this.fileBrowserVisible = parseBoolean(visibilityPreference);
+ }
+ },
+ },
+});
diff --git a/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js b/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js
index 2b1ebb674b4..6235adeb87a 100644
--- a/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js
+++ b/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js
@@ -30,7 +30,6 @@ import { useNotes } from '~/notes/store/legacy_notes';
import {
INLINE_DIFF_VIEW_TYPE,
DIFF_VIEW_COOKIE_NAME,
- MR_TREE_SHOW_KEY,
TREE_LIST_STORAGE_KEY,
OLD_LINE_KEY,
NEW_LINE_KEY,
@@ -718,18 +717,6 @@ export function scrollToFile({ path }) {
}
}
-export function setShowTreeList({ showTreeList, saving = true }) {
- this[types.SET_SHOW_TREE_LIST](showTreeList);
-
- if (saving) {
- localStorage.setItem(MR_TREE_SHOW_KEY, showTreeList);
- }
-}
-
-export function toggleTreeList() {
- this[types.SET_SHOW_TREE_LIST](!this.showTreeList);
-}
-
export function openDiffFileCommentForm(formData) {
const form = this.getCommentFormForDiffFile(formData.fileHash);
diff --git a/app/assets/javascripts/diffs/stores/legacy_diffs/index.js b/app/assets/javascripts/diffs/stores/legacy_diffs/index.js
index 481af7c825a..fcabf732d13 100644
--- a/app/assets/javascripts/diffs/stores/legacy_diffs/index.js
+++ b/app/assets/javascripts/diffs/stores/legacy_diffs/index.js
@@ -36,7 +36,6 @@ export const useLegacyDiffs = defineStore('legacyDiffs', {
diffViewType: INLINE_DIFF_VIEW_TYPE,
tree: [],
treeEntries: {},
- showTreeList: true,
currentDiffFileId: '',
projectPath: '',
viewedDiffFileIds: {},
diff --git a/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js b/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js
index f3af0c55f9d..c2ba48d62fb 100644
--- a/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js
+++ b/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js
@@ -293,9 +293,6 @@ export default {
[types.TREE_ENTRY_DIFF_LOADING]({ path, loading = true }) {
this.treeEntries[path].diffLoading = loading;
},
- [types.SET_SHOW_TREE_LIST](showTreeList) {
- this.showTreeList = showTreeList;
- },
[types.SET_CURRENT_DIFF_FILE](fileId) {
this.currentDiffFileId = fileId;
},
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
index a2ff45b454a..81b42f62b4e 100644
--- a/app/assets/javascripts/pages/projects/issues/show/index.js
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -1,3 +1,24 @@
-import { initShow } from '~/issues';
+import { issuableInitialDataById, isLegacyIssueType } from '~/issues/show/utils/issuable_data';
-initShow();
+const initLegacyIssuePage = async () => {
+ const [{ initShow }] = await Promise.all([import('~/issues')]);
+ initShow();
+};
+
+const initWorkItemPage = async () => {
+ const [{ initWorkItemsRoot }] = await Promise.all([import('~/work_items')]);
+
+ initWorkItemsRoot({ workItemType: 'issue' });
+};
+
+const issuableData = issuableInitialDataById('js-issuable-app');
+
+if (
+ !isLegacyIssueType(issuableData) &&
+ gon.features.workItemsViewPreference &&
+ gon.current_user_use_work_items_view
+) {
+ initWorkItemPage();
+} else {
+ initLegacyIssuePage();
+}
diff --git a/app/assets/javascripts/rapid_diffs/app/index.js b/app/assets/javascripts/rapid_diffs/app/index.js
index 81a2616b4c0..53e63859f55 100644
--- a/app/assets/javascripts/rapid_diffs/app/index.js
+++ b/app/assets/javascripts/rapid_diffs/app/index.js
@@ -4,6 +4,7 @@ import { DiffFile } from '~/rapid_diffs/diff_file';
import { DiffFileMounted } from '~/rapid_diffs/diff_file_mounted';
import { useDiffsList } from '~/rapid_diffs/stores/diffs_list';
import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser';
+import { StreamingError } from '~/rapid_diffs/streaming_error';
// This facade interface joins together all the bits and pieces of Rapid Diffs: DiffFile, Settings, File browser, etc.
// It's a unified entrypoint for Rapid Diffs and all external communications should happen through this interface.
@@ -30,6 +31,7 @@ class RapidDiffsFacade {
#registerCustomElements() {
customElements.define('diff-file', this.DiffFileImplementation);
customElements.define('diff-file-mounted', DiffFileMounted);
+ customElements.define('streaming-error', StreamingError);
}
}
diff --git a/app/assets/javascripts/rapid_diffs/streaming_error.js b/app/assets/javascripts/rapid_diffs/streaming_error.js
new file mode 100644
index 00000000000..916832fc6ff
--- /dev/null
+++ b/app/assets/javascripts/rapid_diffs/streaming_error.js
@@ -0,0 +1,11 @@
+import { createAlert } from '~/alert';
+import { __ } from '~/locale';
+
+export class StreamingError extends HTMLElement {
+ connectedCallback() {
+ const message = __(`Could not fetch all changes. Try reloading the page.`);
+ // eslint-disable-next-line no-console
+ console.error(`Failed to stream diffs: ${this.getAttribute('message')}`);
+ createAlert({ message });
+ }
+}
diff --git a/app/assets/javascripts/whats_new/components/feature.vue b/app/assets/javascripts/whats_new/components/feature.vue
index 3daabcc2798..1900d072a3e 100644
--- a/app/assets/javascripts/whats_new/components/feature.vue
+++ b/app/assets/javascripts/whats_new/components/feature.vue
@@ -3,6 +3,7 @@
import { GlBadge, GlIcon, GlLink, GlButton } from '@gitlab/ui';
import { localeDateFormat, newDate } from '~/lib/utils/datetime_utility';
import SafeHtml from '~/vue_shared/directives/safe_html';
+import { slugify } from '~/lib/utils/text_utility';
export default {
components: {
@@ -27,6 +28,9 @@ export default {
}
return localeDateFormat.asDate.format(newDate(this.feature.published_at));
},
+ descriptionId() {
+ return `${slugify(this.feature.name)}-feature-description`;
+ },
},
};
@@ -40,6 +44,8 @@ export default {
class="gl-block"
data-testid="whats-new-image-link"
data-track-action="click_whats_new_item"
+ :aria-hidden="true"
+ tabindex="-1"
:data-track-label="feature.name"
:data-track-property="feature.documentation_link"
>
@@ -76,6 +82,7 @@ export default {
@@ -85,6 +92,7 @@ export default {
data-track-action="click_whats_new_item"
:data-track-label="feature.name"
:data-track-property="feature.documentation_link"
+ :aria-describedby="descriptionId"
>
{{ __('Learn more') }}
diff --git a/app/assets/javascripts/work_items/components/design_management/design_preview/design_details.vue b/app/assets/javascripts/work_items/components/design_management/design_preview/design_details.vue
index 21ecf14ec42..1e5c8e56752 100644
--- a/app/assets/javascripts/work_items/components/design_management/design_preview/design_details.vue
+++ b/app/assets/javascripts/work_items/components/design_management/design_preview/design_details.vue
@@ -100,7 +100,6 @@ export default {
prevCurrentUserTodos: null,
maxScale: DEFAULT_MAX_SCALE,
workItemId: '',
- workItemTitle: '',
isSidebarOpen: true,
};
},
@@ -360,7 +359,6 @@ export default {
>
- {{ workItemTitle }}
+ {{ design.issue.title }}
-
+
{{ design.filename }}
e
Gitlab::AppLogger.error("Error streaming diffs: #{e.message}")
- response.stream.write e.message
+ error_component = ::RapidDiffs::StreamingErrorComponent.new(message: e.message)
+ response.stream.write error_component.render_in(view_context)
ensure
response.stream.close
end
diff --git a/app/services/ml/create_experiment_service.rb b/app/services/ml/create_experiment_service.rb
index ed312b6e965..8c49856fe15 100644
--- a/app/services/ml/create_experiment_service.rb
+++ b/app/services/ml/create_experiment_service.rb
@@ -2,7 +2,7 @@
module Ml
class CreateExperimentService
- def initialize(project, experiment_name, user = nil)
+ def initialize(project, experiment_name, user)
@project = project
@name = experiment_name
@user = user
diff --git a/app/services/ml/create_model_version_service.rb b/app/services/ml/create_model_version_service.rb
index d9641b1e60f..11e07ff3537 100644
--- a/app/services/ml/create_model_version_service.rb
+++ b/app/services/ml/create_model_version_service.rb
@@ -45,7 +45,7 @@ module Ml
ServiceResponse.success(message: [], payload: { model_version: @model_version })
end
- rescue ActiveRecord::RecordInvalid => e
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
ServiceResponse.error(message: [e.message], payload: { model_version: nil })
rescue ModelVersionCreationError => e
ServiceResponse.error(message: e.errors, payload: { model_version: nil })
diff --git a/db/docs/batched_background_migrations/backfill_root_namespace_cluster_agent_mappings.yml b/db/docs/batched_background_migrations/backfill_root_namespace_cluster_agent_mappings.yml
deleted file mode 100644
index 29551160222..00000000000
--- a/db/docs/batched_background_migrations/backfill_root_namespace_cluster_agent_mappings.yml
+++ /dev/null
@@ -1,10 +0,0 @@
----
-migration_job_name: BackfillRootNamespaceClusterAgentMappings
-description: Creates mappings between root namespaces and all cluster agents
- within these namespaces that have remote development enabled
-feature_category: remote_development
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159051
-milestone: '17.2'
-queued_migration_version: 20240711035245
-# Replace with the approximate date you think it's best to ensure the completion of this BBM.
-finalized_by: 20241023210409
diff --git a/db/docs/deleted_tables/remote_development_namespace_cluster_agent_mappings.yml b/db/docs/deleted_tables/remote_development_namespace_cluster_agent_mappings.yml
new file mode 100644
index 00000000000..fcb2a5e200b
--- /dev/null
+++ b/db/docs/deleted_tables/remote_development_namespace_cluster_agent_mappings.yml
@@ -0,0 +1,15 @@
+---
+table_name: remote_development_namespace_cluster_agent_mappings
+classes:
+ - RemoteDevelopment::RemoteDevelopmentNamespaceClusterAgentMapping
+feature_categories:
+ - workspaces
+description: Moved to deleted because it got renamed to namespace_cluster_agent_mappings
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145101
+milestone: '16.10'
+gitlab_schema: gitlab_main_cell
+sharding_key:
+ namespace_id: namespaces
+table_size: small
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181926
+removed_in_milestone: '17.10'
diff --git a/db/docs/remote_development_namespace_cluster_agent_mappings.yml b/db/docs/namespace_cluster_agent_mappings.yml
similarity index 62%
rename from db/docs/remote_development_namespace_cluster_agent_mappings.yml
rename to db/docs/namespace_cluster_agent_mappings.yml
index 21a94c0092b..465929161c0 100644
--- a/db/docs/remote_development_namespace_cluster_agent_mappings.yml
+++ b/db/docs/namespace_cluster_agent_mappings.yml
@@ -1,14 +1,13 @@
---
-table_name: remote_development_namespace_cluster_agent_mappings
+table_name: namespace_cluster_agent_mappings
classes:
-- RemoteDevelopment::RemoteDevelopmentNamespaceClusterAgentMapping
+- RemoteDevelopment::NamespaceClusterAgentMapping
feature_categories:
- workspaces
description: This table records associations between Namespaces and Cluster Agents
- as a part of supporting group-cluster agent association in the context of Remote
- Development Workspaces
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145101
-milestone: '16.10'
+ as a part of supporting group-cluster agent association in the context of Workspaces
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181926
+milestone: '17.10'
gitlab_schema: gitlab_main_cell
sharding_key:
namespace_id: namespaces
diff --git a/db/docs/packages_debian_project_component_files.yml b/db/docs/packages_debian_project_component_files.yml
index e79d4657684..9bb4574b9a8 100644
--- a/db/docs/packages_debian_project_component_files.yml
+++ b/db/docs/packages_debian_project_component_files.yml
@@ -17,5 +17,4 @@ desired_sharding_key:
table: packages_debian_project_components
sharding_key: project_id
belongs_to: component
- awaiting_backfill_on_parent: true
table_size: small
diff --git a/db/docs/packages_debian_project_components.yml b/db/docs/packages_debian_project_components.yml
index 4a03a0dcf3f..0d457441cf3 100644
--- a/db/docs/packages_debian_project_components.yml
+++ b/db/docs/packages_debian_project_components.yml
@@ -8,14 +8,6 @@ description: Debian package project-level distribution components
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51732
milestone: '13.9'
gitlab_schema: gitlab_main_cell
-desired_sharding_key:
- project_id:
- references: projects
- backfill_via:
- parent:
- foreign_key: distribution_id
- table: packages_debian_project_distributions
- sharding_key: project_id
- belongs_to: distribution
-desired_sharding_key_migration_job_name: BackfillPackagesDebianProjectComponentsProjectId
table_size: small
+sharding_key:
+ project_id: projects
diff --git a/db/docs/packages_maven_metadata.yml b/db/docs/packages_maven_metadata.yml
index b27e84bb0db..d8e2adf28d2 100644
--- a/db/docs/packages_maven_metadata.yml
+++ b/db/docs/packages_maven_metadata.yml
@@ -8,14 +8,6 @@ description: Maven package metadata
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6607
milestone: '11.3'
gitlab_schema: gitlab_main_cell
-desired_sharding_key:
- project_id:
- references: projects
- backfill_via:
- parent:
- foreign_key: package_id
- table: packages_packages
- sharding_key: project_id
- belongs_to: package
-desired_sharding_key_migration_job_name: BackfillPackagesMavenMetadataProjectId
table_size: small
+sharding_key:
+ project_id: projects
diff --git a/db/migrate/20250219055827_rename_remote_development_namespace_cluster_agent_mapping.rb b/db/migrate/20250219055827_rename_remote_development_namespace_cluster_agent_mapping.rb
new file mode 100644
index 00000000000..4d99ae53743
--- /dev/null
+++ b/db/migrate/20250219055827_rename_remote_development_namespace_cluster_agent_mapping.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RenameRemoteDevelopmentNamespaceClusterAgentMapping < Gitlab::Database::Migration[2.2]
+ milestone '17.10'
+
+ def up
+ rename_table_safely(:remote_development_namespace_cluster_agent_mappings, :namespace_cluster_agent_mappings)
+ end
+
+ def down
+ undo_rename_table_safely(:remote_development_namespace_cluster_agent_mappings, :namespace_cluster_agent_mappings)
+ end
+end
diff --git a/db/post_migrate/20240711035245_queue_backfill_root_namespace_cluster_agent_mappings_again.rb b/db/post_migrate/20240711035245_queue_backfill_root_namespace_cluster_agent_mappings_again.rb
deleted file mode 100644
index ec0c4cd76b0..00000000000
--- a/db/post_migrate/20240711035245_queue_backfill_root_namespace_cluster_agent_mappings_again.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-# See https://docs.gitlab.com/ee/development/migration_style_guide.html
-# for more information on how to write migrations for GitLab.
-
-class QueueBackfillRootNamespaceClusterAgentMappingsAgain < Gitlab::Database::Migration[2.2]
- milestone '17.2'
-
- # Configure the `gitlab_schema` to perform data manipulation (DML).
- # Visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html
- restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
- MIGRATION = "BackfillRootNamespaceClusterAgentMappings"
- DELAY_INTERVAL = 2.minutes
- BATCH_SIZE = 1000
- SUB_BATCH_SIZE = 100
-
- # Add dependent 'batched_background_migrations.queued_migration_version' values.
- # DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = []
-
- def up
- delete_batched_background_migration(MIGRATION, :remote_development_agent_configs, :id, [])
-
- queue_batched_background_migration(
- MIGRATION,
- :remote_development_agent_configs,
- :id,
- job_interval: DELAY_INTERVAL,
- batch_size: BATCH_SIZE,
- sub_batch_size: SUB_BATCH_SIZE
- )
- end
-
- def down
- delete_batched_background_migration(MIGRATION, :remote_development_agent_configs, :id, [])
- end
-end
diff --git a/db/post_migrate/20250224063503_delete_remote_development_namespace_cluster_agent_mapping_view.rb b/db/post_migrate/20250224063503_delete_remote_development_namespace_cluster_agent_mapping_view.rb
new file mode 100644
index 00000000000..16e31f84d20
--- /dev/null
+++ b/db/post_migrate/20250224063503_delete_remote_development_namespace_cluster_agent_mapping_view.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class DeleteRemoteDevelopmentNamespaceClusterAgentMappingView < Gitlab::Database::Migration[2.2]
+ milestone '17.10'
+ disable_ddl_transaction!
+
+ def up
+ finalize_table_rename(:remote_development_namespace_cluster_agent_mappings, :namespace_cluster_agent_mappings)
+ end
+
+ def down
+ undo_finalize_table_rename(:remote_development_namespace_cluster_agent_mappings, :namespace_cluster_agent_mappings)
+ end
+end
diff --git a/db/post_migrate/20250225043936_add_packages_debian_project_components_project_id_not_null.rb b/db/post_migrate/20250225043936_add_packages_debian_project_components_project_id_not_null.rb
new file mode 100644
index 00000000000..3935fea5582
--- /dev/null
+++ b/db/post_migrate/20250225043936_add_packages_debian_project_components_project_id_not_null.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AddPackagesDebianProjectComponentsProjectIdNotNull < Gitlab::Database::Migration[2.2]
+ milestone '17.10'
+ disable_ddl_transaction!
+
+ def up
+ add_not_null_constraint :packages_debian_project_components, :project_id
+ end
+
+ def down
+ remove_not_null_constraint :packages_debian_project_components, :project_id
+ end
+end
diff --git a/db/post_migrate/20250225044336_add_packages_maven_metadata_project_id_not_null.rb b/db/post_migrate/20250225044336_add_packages_maven_metadata_project_id_not_null.rb
new file mode 100644
index 00000000000..a4c57bb5590
--- /dev/null
+++ b/db/post_migrate/20250225044336_add_packages_maven_metadata_project_id_not_null.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AddPackagesMavenMetadataProjectIdNotNull < Gitlab::Database::Migration[2.2]
+ milestone '17.10'
+ disable_ddl_transaction!
+
+ def up
+ add_not_null_constraint :packages_maven_metadata, :project_id
+ end
+
+ def down
+ remove_not_null_constraint :packages_maven_metadata, :project_id
+ end
+end
diff --git a/db/post_migrate/20250304084537_add_dora_daily_metrics_project_id_date_index.rb b/db/post_migrate/20250304084537_add_dora_daily_metrics_project_id_date_index.rb
new file mode 100644
index 00000000000..8311d063ec9
--- /dev/null
+++ b/db/post_migrate/20250304084537_add_dora_daily_metrics_project_id_date_index.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddDoraDailyMetricsProjectIdDateIndex < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+
+ milestone '17.10'
+
+ INDEX_NAME = 'index_dora_daily_metrics_on_project_id_and_date'
+ OLD_INDEX_NAME = 'index_dora_daily_metrics_on_project_id'
+
+ def up
+ add_concurrent_index(:dora_daily_metrics, [:project_id, :date], name: INDEX_NAME)
+ remove_concurrent_index_by_name(:dora_daily_metrics, OLD_INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:dora_daily_metrics, :project_id, name: OLD_INDEX_NAME)
+ remove_concurrent_index_by_name(:dora_daily_metrics, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20240711035245 b/db/schema_migrations/20240711035245
deleted file mode 100644
index e5581098857..00000000000
--- a/db/schema_migrations/20240711035245
+++ /dev/null
@@ -1 +0,0 @@
-5bd102fd3ff8463e40c4467317d3f146511ef683945e7c2cabaf8bff7abd3d04
\ No newline at end of file
diff --git a/db/schema_migrations/20250219055827 b/db/schema_migrations/20250219055827
new file mode 100644
index 00000000000..388e56e8551
--- /dev/null
+++ b/db/schema_migrations/20250219055827
@@ -0,0 +1 @@
+166459c440ed6e868c66ddc3b79a5aae27b20d2bc21e489123b6676f8cd97841
\ No newline at end of file
diff --git a/db/schema_migrations/20250224063503 b/db/schema_migrations/20250224063503
new file mode 100644
index 00000000000..85f846fcfe0
--- /dev/null
+++ b/db/schema_migrations/20250224063503
@@ -0,0 +1 @@
+dbdfbe9e0140762dcb606ee83ee5ac6ee6f2f0c4b9a60979f73394a9f81ec2e6
\ No newline at end of file
diff --git a/db/schema_migrations/20250225043936 b/db/schema_migrations/20250225043936
new file mode 100644
index 00000000000..b1170f34b8a
--- /dev/null
+++ b/db/schema_migrations/20250225043936
@@ -0,0 +1 @@
+247a50b157e3bf43bc9a70ae74b1f2be18dfec57f9796cac6813a459a6f0cfee
\ No newline at end of file
diff --git a/db/schema_migrations/20250225044336 b/db/schema_migrations/20250225044336
new file mode 100644
index 00000000000..717fbe6dce8
--- /dev/null
+++ b/db/schema_migrations/20250225044336
@@ -0,0 +1 @@
+0bd459ee0a6ad163f1e2e6ff856328f6898174736af667ec2c9f96ed18cffb1f
\ No newline at end of file
diff --git a/db/schema_migrations/20250304084537 b/db/schema_migrations/20250304084537
new file mode 100644
index 00000000000..75458872294
--- /dev/null
+++ b/db/schema_migrations/20250304084537
@@ -0,0 +1 @@
+c13b43a5493f93febb1d54451741f1e39113a5b8b1d6959408ef4390eba92388
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index e2f601274b1..58f5d22415f 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -16913,6 +16913,24 @@ CREATE TABLE namespace_ci_cd_settings (
allow_stale_runner_pruning boolean DEFAULT false NOT NULL
);
+CREATE TABLE namespace_cluster_agent_mappings (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ namespace_id bigint NOT NULL,
+ cluster_agent_id bigint NOT NULL,
+ creator_id bigint
+);
+
+CREATE SEQUENCE namespace_cluster_agent_mappings_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE namespace_cluster_agent_mappings_id_seq OWNED BY namespace_cluster_agent_mappings.id;
+
CREATE TABLE namespace_commit_emails (
id bigint NOT NULL,
user_id bigint NOT NULL,
@@ -18100,7 +18118,8 @@ CREATE TABLE packages_debian_project_components (
distribution_id bigint NOT NULL,
name text NOT NULL,
project_id bigint,
- CONSTRAINT check_517559f298 CHECK ((char_length(name) <= 255))
+ CONSTRAINT check_517559f298 CHECK ((char_length(name) <= 255)),
+ CONSTRAINT check_6c727037a7 CHECK ((project_id IS NOT NULL))
);
CREATE SEQUENCE packages_debian_project_components_id_seq
@@ -18247,7 +18266,8 @@ CREATE TABLE packages_maven_metadata (
app_name character varying NOT NULL,
app_version character varying,
path character varying(512) NOT NULL,
- project_id bigint
+ project_id bigint,
+ CONSTRAINT check_bf287ce98c CHECK ((project_id IS NOT NULL))
);
CREATE SEQUENCE packages_maven_metadata_id_seq
@@ -20745,24 +20765,6 @@ CREATE SEQUENCE releases_id_seq
ALTER SEQUENCE releases_id_seq OWNED BY releases.id;
-CREATE TABLE remote_development_namespace_cluster_agent_mappings (
- id bigint NOT NULL,
- created_at timestamp with time zone NOT NULL,
- updated_at timestamp with time zone NOT NULL,
- namespace_id bigint NOT NULL,
- cluster_agent_id bigint NOT NULL,
- creator_id bigint
-);
-
-CREATE SEQUENCE remote_development_namespace_cluster_agent_mappings_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-ALTER SEQUENCE remote_development_namespace_cluster_agent_mappings_id_seq OWNED BY remote_development_namespace_cluster_agent_mappings.id;
-
CREATE TABLE remote_mirrors (
id bigint NOT NULL,
project_id bigint,
@@ -25891,6 +25893,8 @@ ALTER TABLE ONLY namespace_admin_notes ALTER COLUMN id SET DEFAULT nextval('name
ALTER TABLE ONLY namespace_bans ALTER COLUMN id SET DEFAULT nextval('namespace_bans_id_seq'::regclass);
+ALTER TABLE ONLY namespace_cluster_agent_mappings ALTER COLUMN id SET DEFAULT nextval('namespace_cluster_agent_mappings_id_seq'::regclass);
+
ALTER TABLE ONLY namespace_commit_emails ALTER COLUMN id SET DEFAULT nextval('namespace_commit_emails_id_seq'::regclass);
ALTER TABLE ONLY namespace_import_users ALTER COLUMN id SET DEFAULT nextval('namespace_import_users_id_seq'::regclass);
@@ -26167,8 +26171,6 @@ ALTER TABLE ONLY release_links ALTER COLUMN id SET DEFAULT nextval('release_link
ALTER TABLE ONLY releases ALTER COLUMN id SET DEFAULT nextval('releases_id_seq'::regclass);
-ALTER TABLE ONLY remote_development_namespace_cluster_agent_mappings ALTER COLUMN id SET DEFAULT nextval('remote_development_namespace_cluster_agent_mappings_id_seq'::regclass);
-
ALTER TABLE ONLY remote_mirrors ALTER COLUMN id SET DEFAULT nextval('remote_mirrors_id_seq'::regclass);
ALTER TABLE ONLY required_code_owners_sections ALTER COLUMN id SET DEFAULT nextval('required_code_owners_sections_id_seq'::regclass);
@@ -28470,6 +28472,9 @@ ALTER TABLE ONLY namespace_bans
ALTER TABLE ONLY namespace_ci_cd_settings
ADD CONSTRAINT namespace_ci_cd_settings_pkey PRIMARY KEY (namespace_id);
+ALTER TABLE ONLY namespace_cluster_agent_mappings
+ ADD CONSTRAINT namespace_cluster_agent_mappings_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY namespace_commit_emails
ADD CONSTRAINT namespace_commit_emails_pkey PRIMARY KEY (id);
@@ -29019,9 +29024,6 @@ ALTER TABLE releases
ALTER TABLE ONLY releases
ADD CONSTRAINT releases_pkey PRIMARY KEY (id);
-ALTER TABLE ONLY remote_development_namespace_cluster_agent_mappings
- ADD CONSTRAINT remote_development_namespace_cluster_agent_mappings_pkey PRIMARY KEY (id);
-
ALTER TABLE ONLY remote_mirrors
ADD CONSTRAINT remote_mirrors_pkey PRIMARY KEY (id);
@@ -31095,9 +31097,9 @@ CREATE UNIQUE INDEX i_duo_workflows_events_on_correlation_id ON duo_workflows_ev
CREATE INDEX i_gitlab_subscription_histories_on_namespace_change_type_plan ON gitlab_subscription_histories USING btree (namespace_id, change_type, hosted_plan_id);
-CREATE INDEX i_namespace_cluster_agent_mappings_on_cluster_agent_id ON remote_development_namespace_cluster_agent_mappings USING btree (cluster_agent_id);
+CREATE INDEX i_namespace_cluster_agent_mappings_on_cluster_agent_id ON namespace_cluster_agent_mappings USING btree (cluster_agent_id);
-CREATE INDEX i_namespace_cluster_agent_mappings_on_creator_id ON remote_development_namespace_cluster_agent_mappings USING btree (creator_id);
+CREATE INDEX i_namespace_cluster_agent_mappings_on_creator_id ON namespace_cluster_agent_mappings USING btree (creator_id);
CREATE INDEX i_organization_cluster_agent_mappings_on_creator_id ON organization_cluster_agent_mappings USING btree (creator_id);
@@ -32981,7 +32983,7 @@ CREATE UNIQUE INDEX index_dora_configurations_on_project_id ON dora_configuratio
CREATE UNIQUE INDEX index_dora_daily_metrics_on_environment_id_and_date ON dora_daily_metrics USING btree (environment_id, date);
-CREATE INDEX index_dora_daily_metrics_on_project_id ON dora_daily_metrics USING btree (project_id);
+CREATE INDEX index_dora_daily_metrics_on_project_id_and_date ON dora_daily_metrics USING btree (project_id, date);
CREATE UNIQUE INDEX index_dora_performance_scores_on_project_id_and_date ON dora_performance_scores USING btree (project_id, date);
@@ -36441,7 +36443,7 @@ CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_re
CREATE INDEX unique_ml_model_versions_on_model_id_and_id ON ml_model_versions USING btree (model_id, id DESC);
-CREATE UNIQUE INDEX unique_namespace_cluster_agent_mappings_for_agent_association ON remote_development_namespace_cluster_agent_mappings USING btree (namespace_id, cluster_agent_id);
+CREATE UNIQUE INDEX unique_namespace_cluster_agent_mappings_for_agent_association ON namespace_cluster_agent_mappings USING btree (namespace_id, cluster_agent_id);
CREATE UNIQUE INDEX unique_organizations_on_path_case_insensitive ON organizations USING btree (lower(path));
@@ -39016,7 +39018,7 @@ ALTER TABLE ONLY audit_events_amazon_s3_configurations
ALTER TABLE ONLY issue_customer_relations_contacts
ADD CONSTRAINT fk_0c0037f723 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
-ALTER TABLE ONLY remote_development_namespace_cluster_agent_mappings
+ALTER TABLE ONLY namespace_cluster_agent_mappings
ADD CONSTRAINT fk_0c483ecb9d FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY zoekt_replicas
@@ -39067,7 +39069,7 @@ ALTER TABLE ONLY protected_environment_deploy_access_levels
ALTER TABLE ONLY cluster_agent_migrations
ADD CONSTRAINT fk_1211a345fb FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
-ALTER TABLE ONLY remote_development_namespace_cluster_agent_mappings
+ALTER TABLE ONLY namespace_cluster_agent_mappings
ADD CONSTRAINT fk_124d8167c5 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY cluster_agent_url_configurations
@@ -40441,7 +40443,7 @@ ALTER TABLE ONLY ci_sources_pipelines
ALTER TABLE ONLY packages_maven_metadata
ADD CONSTRAINT fk_be88aed360 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
-ALTER TABLE ONLY remote_development_namespace_cluster_agent_mappings
+ALTER TABLE ONLY namespace_cluster_agent_mappings
ADD CONSTRAINT fk_be8e9c740f FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
ALTER TABLE ONLY oauth_device_grants
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index cee273920e4..8883225401f 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -4024,6 +4024,7 @@ Input type: `CreateComplianceRequirementInput`
| ---- | ---- | ----------- |
| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| `complianceFrameworkId` | [`ComplianceManagementFrameworkID!`](#compliancemanagementframeworkid) | Global ID of the compliance framework of the new requirement. |
+| `controls` | [`[ComplianceRequirementsControlInput!]`](#compliancerequirementscontrolinput) | Controls to add to the compliance requirement. |
| `params` | [`ComplianceRequirementInput!`](#compliancerequirementinput) | Parameters to update the compliance requirement with. |
#### Fields
@@ -26427,7 +26428,7 @@ GPG signature for a signed commit.
| `descendantGroupsCount` | [`Int!`](#int) | Count of direct descendant groups of this group. |
| `description` | [`String`](#string) | Description of the namespace. |
| `descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
-| `dora` | [`Dora`](#dora) | Group's DORA metrics. |
+| `dora` | [`GroupDora`](#groupdora) | Group's DORA metrics. |
| `duoFeaturesEnabled` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Introduced** in GitLab 16.10. **Status**: Experiment. Indicates whether GitLab Duo features are enabled for the group. |
| `emailsDisabled` | [`Boolean`](#boolean) | Indicates if a group has email notifications disabled. |
| `emailsEnabled` | [`Boolean`](#boolean) | Indicates if a group has email notifications enabled. |
@@ -28175,6 +28176,58 @@ Represents an external destination to stream group level audit events.
| ---- | ---- | ----------- |
| `egressNodes` | [`EgressNodeConnection`](#egressnodeconnection) | Data nodes. (see [Connections](#connections)) |
+### `GroupDora`
+
+All information related to group DORA metrics.
+
+#### Fields with arguments
+
+##### `GroupDora.metrics`
+
+DORA metrics for the current group or project.
+
+Returns [`[DoraMetric!]`](#dorametric).
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `endDate` | [`Date`](#date) | Date range to end at. Default is the current date. |
+| `environmentTiers` | [`[DeploymentTier!]`](#deploymenttier) | Deployment tiers of the environments to return. Defaults to `[PRODUCTION]`. |
+| `interval` | [`DoraMetricBucketingInterval`](#dorametricbucketinginterval) | How the metric should be aggregated. Defaults to `DAILY`. In the case of `ALL`, the `date` field in the response will be `null`. |
+| `startDate` | [`Date`](#date) | Date range to start from. Default is 3 months ago. |
+
+##### `GroupDora.projects`
+
+Projects within this group with at least 1 DORA metric for given period.
+
+Returns [`ProjectConnection!`](#projectconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `complianceFrameworkFilters` | [`ComplianceFrameworkFilters`](#complianceframeworkfilters) | Filters applied when selecting a compliance framework. |
+| `endDate` | [`Date!`](#date) | Date range to end DORA lookup at. |
+| `hasCodeCoverage` | [`Boolean`](#boolean) | Returns only the projects which have code coverage. |
+| `hasVulnerabilities` | [`Boolean`](#boolean) | Returns only the projects which have vulnerabilities. |
+| `ids` | [`[ID!]`](#id) | Filter projects by IDs. |
+| `includeArchived` | [`Boolean`](#boolean) | Include also archived projects. |
+| `includeSiblingProjects` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Introduced** in GitLab 17.2. **Status**: Experiment. Include also projects from parent group. |
+| `includeSubgroups` | [`Boolean`](#boolean) | Include also subgroup projects. |
+| `notAimedForDeletion` | [`Boolean`](#boolean) | Include projects that are not aimed for deletion. |
+| `sbomComponentId` | [`ID`](#id) | Return only the projects related to the specified SBOM component. |
+| `search` | [`String`](#string) | Search project with most similar names or paths. |
+| `sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by the criteria. |
+| `startDate` | [`Date!`](#date) | Date range to start DORA lookup from. |
+| `withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. |
+| `withMergeRequestsEnabled` | [`Boolean`](#boolean) | Return only projects with merge requests enabled. |
+| `withNamespaceDomainPages` | [`Boolean`](#boolean) | Return only projects that use the namespace domain for pages projects. |
+
### `GroupMember`
Represents a Group Membership.
@@ -46915,9 +46968,9 @@ Attributes for defining a CI/CD variable.
| Name | Type | Description |
| ---- | ---- | ----------- |
| `controlType` | [`String`](#string) | Type of the compliance control. |
-| `expression` | [`String`](#string) | Expression of the compliance control. |
+| `expression` | [`String!`](#string) | Expression of the compliance control. |
| `externalUrl` | [`String`](#string) | URL of the external control. |
-| `name` | [`String`](#string) | New name for the compliance requirement control. |
+| `name` | [`String!`](#string) | New name for the compliance requirement control. |
| `secretToken` | [`String`](#string) | Secret token for an external control. |
### `ComplianceStandardsAdherenceInput`
diff --git a/doc/user/gitlab_duo_chat/examples.md b/doc/user/gitlab_duo_chat/examples.md
index ec3320015e6..c52bc16e55a 100644
--- a/doc/user/gitlab_duo_chat/examples.md
+++ b/doc/user/gitlab_duo_chat/examples.md
@@ -344,7 +344,6 @@ Programming languages that require compiling the source code may throw cryptic e
The availability of this feature is controlled by a feature flag.
For more information, see the history.
-GitLab.com customers must contact their Customer Success Manager to enable this feature.
{{< /alert >}}
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e24fd3cf5af..421c9b86cc8 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -361,10 +361,6 @@ module API
mount ::API::Users
mount ::API::UserCounts
mount ::API::UserRunners
- mount ::API::VirtualRegistries::Packages::Maven::Registries
- mount ::API::VirtualRegistries::Packages::Maven::Upstreams
- mount ::API::VirtualRegistries::Packages::Maven::Cache::Entries
- mount ::API::VirtualRegistries::Packages::Maven::Endpoints
mount ::API::WebCommits
mount ::API::Wikis
diff --git a/lib/api/concerns/virtual_registries/packages/endpoint.rb b/lib/api/concerns/virtual_registries/packages/endpoint.rb
deleted file mode 100644
index adff635b930..00000000000
--- a/lib/api/concerns/virtual_registries/packages/endpoint.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Concerns
- module VirtualRegistries
- module Packages
- module Endpoint
- extend ActiveSupport::Concern
-
- 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.'
- UPSTREAM_GID_HEADER = 'X-Gitlab-Virtual-Registry-Upstream-Global-Id'
- MAX_FILE_SIZE = 5.gigabytes
-
- included do
- helpers do
- def require_non_web_browser!
- browser = ::Browser.new(request.user_agent)
- bad_request!(WEB_BROWSER_ERROR_MESSAGE) if MAJOR_BROWSERS.any? { |b| browser.method(:"#{b}?").call }
- end
-
- def send_successful_response_from(service_response:)
- action, action_params = service_response.to_h.values_at(:action, :action_params)
- case action
- when :workhorse_upload_url
- workhorse_upload_url(**action_params.slice(:url, :upstream))
- when :download_file
- extra_response_headers = download_file_extra_response_headers(action_params: action_params)
- present_carrierwave_file!(
- action_params[:file],
- supports_direct_download: extra_response_headers.blank?,
- content_type: action_params[:content_type],
- content_disposition: 'inline',
- extra_response_headers: extra_response_headers
- )
- when :download_digest
- content_type 'text/plain'
- env['api.format'] = :binary # to return data as-is
- body action_params[:digest]
- end
- end
-
- def send_error_response_from!(service_response:)
- case service_response.reason
- when :unauthorized
- unauthorized!
- when :file_not_found_on_upstreams, :digest_not_found_in_cache_entries
- not_found!(service_response.message)
- else
- bad_request!(service_response.message)
- end
- end
-
- def workhorse_upload_url(url:, upstream:)
- allow_localhost = Gitlab.dev_or_test_env? ||
- Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
- allowed_uris = ObjectStoreSettings.enabled_endpoint_uris
- send_workhorse_headers(
- Gitlab::Workhorse.send_dependency(
- upstream.headers,
- url,
- response_headers: NO_BROWSER_EXECUTION_RESPONSE_HEADERS,
- allow_localhost: allow_localhost,
- allowed_uris: allowed_uris,
- ssrf_filter: true,
- upload_config: {
- headers: { UPSTREAM_GID_HEADER => upstream.to_global_id.to_s },
- authorized_upload_response: authorized_upload_response
- }
- )
- )
- end
-
- def authorized_upload_response
- ::VirtualRegistries::Cache::EntryUploader.workhorse_authorize(
- has_length: true,
- maximum_size: MAX_FILE_SIZE,
- use_final_store_path: true,
- final_store_path_config: {
- override_path: upstream.object_storage_key_for(registry_id: registry.id)
- }
- )
- end
-
- def send_workhorse_headers(headers)
- header(*headers)
- env['api.format'] = :binary
- content_type 'application/octet-stream'
- status :ok
- body ''
- end
-
- def ok_empty_response
- status :ok
- env['api.format'] = :binary # to return data as-is
- body ''
- end
- end
-
- after_validation do
- require_non_web_browser!
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/entities/virtual_registries/packages/maven/cache/entry.rb b/lib/api/entities/virtual_registries/packages/maven/cache/entry.rb
deleted file mode 100644
index c8c3b67694c..00000000000
--- a/lib/api/entities/virtual_registries/packages/maven/cache/entry.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module VirtualRegistries
- module Packages
- module Maven
- module Cache
- class Entry < Grape::Entity
- expose :id do |cache_entry, _options|
- Base64.urlsafe_encode64("#{cache_entry.upstream_id} #{cache_entry.relative_path}")
- end
-
- expose :group_id,
- :upstream_id,
- :upstream_checked_at,
- :file_md5,
- :file_sha1,
- :size,
- :relative_path,
- :upstream_etag,
- :content_type,
- :created_at,
- :updated_at
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/entities/virtual_registries/packages/maven/registry.rb b/lib/api/entities/virtual_registries/packages/maven/registry.rb
deleted file mode 100644
index 73f4e0c66c8..00000000000
--- a/lib/api/entities/virtual_registries/packages/maven/registry.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module VirtualRegistries
- module Packages
- module Maven
- class Registry < Grape::Entity
- expose :id, :group_id, :created_at, :updated_at
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/entities/virtual_registries/packages/maven/upstream.rb b/lib/api/entities/virtual_registries/packages/maven/upstream.rb
deleted file mode 100644
index 0743b6feed3..00000000000
--- a/lib/api/entities/virtual_registries/packages/maven/upstream.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module VirtualRegistries
- module Packages
- module Maven
- class Upstream < Grape::Entity
- expose :id, :group_id, :url, :cache_validity_hours, :created_at, :updated_at
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/ml/mlflow/runs.rb b/lib/api/ml/mlflow/runs.rb
index 8315e63ea20..2935371f00c 100644
--- a/lib/api/ml/mlflow/runs.rb
+++ b/lib/api/ml/mlflow/runs.rb
@@ -14,6 +14,8 @@ module API
check_api_write! unless request.get? || request.head?
end
+ CANDIDATE_STATES = ::Ml::Candidate.statuses.keys.map(&:upcase).freeze
+
resource :runs do
desc 'Creates a Run.' do
success Entities::Ml::Mlflow::Run
@@ -93,9 +95,9 @@ module API
params do
requires :run_id, type: String, desc: 'UUID of the candidate.'
optional :status, type: String,
- values: ::Ml::Candidate.statuses.keys.map(&:upcase),
+ values: CANDIDATE_STATES,
desc: "Status of the run. Accepts: " \
- "#{::Ml::Candidate.statuses.keys.map(&:upcase)}."
+ "#{CANDIDATE_STATES}."
optional :end_time, type: Integer, desc: 'Ending time of the run'
end
post 'update', urgency: :low do
diff --git a/lib/api/virtual_registries/packages/maven/cache/entries.rb b/lib/api/virtual_registries/packages/maven/cache/entries.rb
deleted file mode 100644
index f900b3f1f3b..00000000000
--- a/lib/api/virtual_registries/packages/maven/cache/entries.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module VirtualRegistries
- module Packages
- module Maven
- module Cache
- class Entries < ::API::Base
- include ::API::Helpers::Authentication
- include ::API::PaginationParams
-
- feature_category :virtual_registry
- urgency :low
-
- 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)
- accept.token_types(:job_token).sent_through(:http_job_token_header)
- end
-
- helpers do
- include ::Gitlab::Utils::StrongMemoize
-
- def require_dependency_proxy_enabled!
- not_found! unless ::Gitlab.config.dependency_proxy.enabled
- end
-
- def upstream
- ::VirtualRegistries::Packages::Maven::Upstream.find(params[:id])
- end
- strong_memoize_attr :upstream
-
- def cache_entries
- upstream.default_cache_entries.order_created_desc.search_by_relative_path(params[:search])
- end
-
- def cache_entry
- ::VirtualRegistries::Packages::Maven::Cache::Entry
- .default
- .find_by_upstream_id_and_relative_path!(*declared_params[:id].split)
- end
- strong_memoize_attr :cache_entry
- end
-
- after_validation do
- not_found! unless Feature.enabled?(:virtual_registry_maven, current_user)
-
- require_dependency_proxy_enabled!
-
- authenticate!
- end
-
- namespace 'virtual_registries/packages/maven' do
- namespace :upstreams do
- route_param :id, type: Integer, desc: 'The ID of the maven virtual registry upstream' do
- namespace :cache_entries do
- desc 'List maven virtual registry upstream cache entries' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in an experimental state. \
- This feature is behind the `virtual_registry_maven` feature flag.'
- success ::API::Entities::VirtualRegistries::Packages::Maven::Cache::Entry
- failure [
- { code: 400, message: 'Bad Request' },
- { code: 401, message: 'Unauthorized' },
- { code: 403, message: 'Forbidden' },
- { code: 404, message: 'Not found' }
- ]
- tags %w[maven_virtual_registries]
- is_array true
- hidden true
- end
-
- params do
- optional :search, type: String, desc: 'Search query', documentation: { example: 'foo/bar/mypkg' }
- use :pagination
- end
- get do
- authorize! :read_virtual_registry, upstream
-
- present paginate(cache_entries),
- with: ::API::Entities::VirtualRegistries::Packages::Maven::Cache::Entry
- end
- end
- end
- end
-
- namespace :cache_entries do
- desc 'Delete a maven virtual registry upstream cache entry' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in an experimental state. \
- This feature is behind the `virtual_registry_maven` feature flag.'
- success code: 204
- 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: String, coerce_with: Base64.method(:urlsafe_decode64),
- desc: 'The base64 encoded upstream id and relative path of the cache entry',
- documentation: { example: 'Zm9vL2Jhci9teXBrZy5wb20=' }
- end
-
- delete '*id' do
- authorize! :destroy_virtual_registry, cache_entry.upstream
-
- destroy_conditionally!(cache_entry) do |cache_entry|
- render_validation_error!(cache_entry) unless cache_entry.mark_as_pending_destruction
- end
- end
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/virtual_registries/packages/maven/endpoints.rb b/lib/api/virtual_registries/packages/maven/endpoints.rb
deleted file mode 100644
index 5437850785d..00000000000
--- a/lib/api/virtual_registries/packages/maven/endpoints.rb
+++ /dev/null
@@ -1,165 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module VirtualRegistries
- module Packages
- module Maven
- class Endpoints < ::API::Base
- include ::API::Helpers::Authentication
- include ::API::Concerns::VirtualRegistries::Packages::Endpoint
- include ::API::APIGuard
-
- feature_category :virtual_registry
- urgency :low
-
- AUTHENTICATE_REALM_HEADER = 'WWW-Authenticate'
- AUTHENTICATE_REALM_NAME = 'Basic realm="GitLab Virtual Registry"'
-
- SHA1_CHECKSUM_HEADER = 'x-checksum-sha1'
- MD5_CHECKSUM_HEADER = 'x-checksum-md5'
-
- 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)
- accept.token_types(:job_token).sent_through(:http_job_token_header)
-
- accept.token_types(
- :personal_access_token_with_username,
- :deploy_token_with_username,
- :job_token_with_username
- ).sent_through(:http_basic_auth)
- end
-
- allow_access_with_scope :read_virtual_registry
-
- helpers do
- include ::Gitlab::Utils::StrongMemoize
-
- delegate :group, :upstream, :registry_upstream, to: :registry
-
- def require_dependency_proxy_enabled!
- not_found! unless ::Gitlab.config.dependency_proxy.enabled
- end
-
- def registry
- ::VirtualRegistries::Packages::Maven::Registry.find(params[:id])
- end
- strong_memoize_attr :registry
-
- def download_file_extra_response_headers(action_params:)
- {
- SHA1_CHECKSUM_HEADER => action_params[:file_sha1],
- MD5_CHECKSUM_HEADER => action_params[:file_md5]
- }
- end
-
- # override from api helpers unauthorized! function
- def unauthorized!(reason = nil)
- header(AUTHENTICATE_REALM_HEADER, AUTHENTICATE_REALM_NAME)
- super
- end
-
- 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
- not_found! unless Feature.enabled?(:virtual_registry_maven, current_user)
-
- require_dependency_proxy_enabled!
-
- authenticate!
- end
-
- namespace 'virtual_registries/packages/maven/:id/*path' do
- 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: 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
-
- 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 'upload' do
- require_gitlab_workhorse!
- authorize!(:read_virtual_registry, registry)
-
- 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::Cache::Entries::CreateOrUpdateService.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?
- ok_empty_response
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/virtual_registries/packages/maven/registries.rb b/lib/api/virtual_registries/packages/maven/registries.rb
deleted file mode 100644
index 7ab9678411f..00000000000
--- a/lib/api/virtual_registries/packages/maven/registries.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module VirtualRegistries
- module Packages
- module Maven
- class Registries < ::API::Base
- include ::API::Helpers::Authentication
-
- feature_category :virtual_registry
- urgency :low
-
- 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)
- accept.token_types(:job_token).sent_through(:http_job_token_header)
- end
-
- helpers do
- include ::Gitlab::Utils::StrongMemoize
-
- def group
- find_group!(params[:id])
- end
- strong_memoize_attr :group
-
- def registry
- ::VirtualRegistries::Packages::Maven::Registry.find(params[:id])
- end
- strong_memoize_attr :registry
-
- def policy_subject
- ::VirtualRegistries::Packages::Policies::Group.new(group)
- end
-
- def require_dependency_proxy_enabled!
- not_found! unless ::Gitlab.config.dependency_proxy.enabled
- end
- end
-
- after_validation do
- not_found! unless Feature.enabled?(:virtual_registry_maven, current_user)
-
- require_dependency_proxy_enabled!
-
- authenticate!
- end
-
- resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- params do
- requires :id, types: [String, Integer], desc: 'The group ID or full group path. Must be a top-level group'
- end
-
- namespace ':id/-/virtual_registries/packages/maven/registries' do
- desc 'Get the list of all maven virtual registries' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in an experimental state. \
- This feature is behind the `virtual_registry_maven` feature flag.'
- success ::API::Entities::VirtualRegistries::Packages::Maven::Registry
- 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
-
- get do
- authorize! :read_virtual_registry, policy_subject
-
- registries = ::VirtualRegistries::Packages::Maven::Registry.for_group(group)
-
- present registries, with: ::API::Entities::VirtualRegistries::Packages::Maven::Registry
- end
-
- desc 'Create a new maven virtual registry' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in an experimental state. \
- This feature is behind the `virtual_registry_maven` feature flag.'
- success ::API::Entities::VirtualRegistries::Packages::Maven::Registry
- 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
-
- post do
- authorize! :create_virtual_registry, policy_subject
-
- new_reg = ::VirtualRegistries::Packages::Maven::Registry.new(group:)
-
- render_validation_error!(new_reg) unless new_reg.save
-
- present new_reg, with: ::API::Entities::VirtualRegistries::Packages::Maven::Registry
- end
- end
- end
-
- namespace 'virtual_registries/packages/maven/registries' do
- route_param :id, type: Integer, desc: 'The ID of the maven virtual registry' do
- desc 'Get a specific maven virtual registry' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in an experimental state. \
- This feature is behind the `virtual_registry_maven` feature flag.'
- success ::API::Entities::VirtualRegistries::Packages::Maven::Registry
- 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
- get do
- authorize! :read_virtual_registry, registry
-
- present registry, with: ::API::Entities::VirtualRegistries::Packages::Maven::Registry
- end
-
- desc 'Delete a specific maven virtual registry' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in an experimental state. \
- This feature is behind the `virtual_registry_maven` feature flag.'
- success code: 204
- failure [
- { code: 400, message: 'Bad request' },
- { code: 401, message: 'Unauthorized' },
- { code: 403, message: 'Forbidden' },
- { code: 404, message: 'Not found' },
- { code: 412, message: 'Precondition Failed' }
- ]
- tags %w[maven_virtual_registries]
- hidden true
- end
- delete do
- authorize! :destroy_virtual_registry, registry
-
- destroy_conditionally!(registry)
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/virtual_registries/packages/maven/upstreams.rb b/lib/api/virtual_registries/packages/maven/upstreams.rb
deleted file mode 100644
index 8bb1c901191..00000000000
--- a/lib/api/virtual_registries/packages/maven/upstreams.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module VirtualRegistries
- module Packages
- module Maven
- class Upstreams < ::API::Base
- include ::API::Helpers::Authentication
-
- feature_category :virtual_registry
- urgency :low
-
- 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)
- accept.token_types(:job_token).sent_through(:http_job_token_header)
- end
-
- helpers do
- include ::Gitlab::Utils::StrongMemoize
-
- delegate :group, :registry_upstream, to: :registry
-
- def require_dependency_proxy_enabled!
- not_found! unless Gitlab.config.dependency_proxy.enabled
- end
-
- def registry
- ::VirtualRegistries::Packages::Maven::Registry.find(params[:id])
- end
- strong_memoize_attr :registry
-
- def upstream
- ::VirtualRegistries::Packages::Maven::Upstream.find(params[:id])
- end
- strong_memoize_attr :upstream
- end
-
- after_validation do
- not_found! unless Feature.enabled?(:virtual_registry_maven, current_user)
-
- require_dependency_proxy_enabled!
-
- authenticate!
- end
-
- namespace 'virtual_registries/packages/maven' do
- namespace :registries do
- route_param :id, type: Integer, desc: 'The ID of the maven virtual registry' do
- namespace :upstreams do
- desc 'List all maven virtual registry upstreams' do
- detail 'This feature was introduced in GitLab 17.4. \
- 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
- get do
- authorize! :read_virtual_registry, registry
-
- present [registry.upstream].compact, with: Entities::VirtualRegistries::Packages::Maven::Upstream
- end
-
- desc 'Add a maven virtual registry upstream' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in experiment state. \
- This feature behind the `virtual_registry_maven` feature flag.'
- success code: 201, model: ::API::Entities::VirtualRegistries::Packages::Maven::Upstream
- failure [
- { code: 400, message: 'Bad Request' },
- { code: 401, message: 'Unauthorized' },
- { code: 403, message: 'Forbidden' },
- { code: 404, message: 'Not found' },
- { code: 409, message: 'Conflict' }
- ]
- tags %w[maven_virtual_registries]
- hidden true
- end
- params do
- requires :url, type: String, desc: 'The URL of the maven virtual registry upstream',
- allow_blank: false
- optional :username, type: String, desc: 'The username of the maven virtual registry upstream'
- optional :password, type: String, desc: 'The password of the maven virtual registry upstream'
- optional :cache_validity_hours, type: Integer, desc: 'The cache validity in hours. Defaults to 24'
- all_or_none_of :username, :password
- end
- post do
- authorize! :create_virtual_registry, registry
-
- conflict!(_('Upstream already exists')) if registry.upstream
-
- new_upstream = registry.build_upstream(declared_params(include_missing: false).merge(group:))
- registry_upstream.group = group
-
- ApplicationRecord.transaction do
- render_validation_error!(new_upstream) unless new_upstream.save
- render_validation_error!(registry_upstream) unless registry_upstream.save
- end
-
- present new_upstream, with: Entities::VirtualRegistries::Packages::Maven::Upstream
- end
- end
- end
- end
-
- namespace :upstreams do
- route_param :id, type: Integer, desc: 'The ID of the maven virtual registry upstream' do
- desc 'Get a specific maven virtual registry upstream' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in experiment state. \
- This feature behind the `virtual_registry_maven` feature flag.'
- success ::API::Entities::VirtualRegistries::Packages::Maven::Upstream
- 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
- get do
- authorize! :read_virtual_registry, upstream
-
- present upstream, with: ::API::Entities::VirtualRegistries::Packages::Maven::Upstream
- end
-
- desc 'Update a maven virtual registry upstream' do
- detail 'This feature was introduced in GitLab 17.4. \
- 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
- with(allow_blank: false) do
- optional :url, type: String, desc: 'The URL of the maven virtual registry upstream'
- optional :username, type: String, desc: 'The username of the maven virtual registry upstream'
- optional :password, type: String, desc: 'The password of the maven virtual registry upstream'
- optional :cache_validity_hours, type: Integer, desc: 'The validity of the cache in hours'
- end
-
- at_least_one_of :url, :username, :password, :cache_validity_hours
- end
- patch do
- authorize! :update_virtual_registry, upstream
-
- render_validation_error!(upstream) unless upstream.update(declared_params(include_missing: false))
-
- status :ok
- end
-
- desc 'Delete a maven virtual registry upstream' do
- detail 'This feature was introduced in GitLab 17.4. \
- This feature is currently in experiment state. \
- This feature behind the `virtual_registry_maven` feature flag.'
- success code: 204
- 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
- delete do
- authorize! :destroy_virtual_registry, upstream
-
- destroy_conditionally!(upstream)
- end
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/backfill_root_namespace_cluster_agent_mappings.rb b/lib/gitlab/background_migration/backfill_root_namespace_cluster_agent_mappings.rb
deleted file mode 100644
index 3e143ba2534..00000000000
--- a/lib/gitlab/background_migration/backfill_root_namespace_cluster_agent_mappings.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Backfills mappings between root namespaces and all agents within the root namespace
- # that have remote development module enabled
- # For more details, check: https://gitlab.com/gitlab-org/gitlab/-/issues/454411
- class BackfillRootNamespaceClusterAgentMappings < BatchedMigrationJob
- feature_category :workspaces
-
- def perform; end
- end
- end
-end
-
-Gitlab::BackgroundMigration::BackfillRootNamespaceClusterAgentMappings.prepend_mod
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index e14cc2e80c2..9eeec0482ed 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -12,9 +12,7 @@ module Gitlab
# TABLES_TO_BE_RENAMED = {
# 'old_name' => 'new_name'
# }.freeze
- TABLES_TO_BE_RENAMED = {
- 'remote_development_namespace_cluster_agent_mappings' => 'namespace_cluster_agent_mappings'
- }.freeze
+ TABLES_TO_BE_RENAMED = {}.freeze
# Minimum PostgreSQL version requirement per documentation:
# https://docs.gitlab.com/ee/install/requirements.html#postgresql-requirements
diff --git a/lib/web_ide/settings/default_settings.rb b/lib/web_ide/settings/default_settings.rb
index e3d03cbe19d..19df3da3b06 100644
--- a/lib/web_ide/settings/default_settings.rb
+++ b/lib/web_ide/settings/default_settings.rb
@@ -10,10 +10,12 @@ module WebIde
vscode_extension_marketplace: [
# See https://sourcegraph.com/github.com/microsoft/vscode@6979fb003bfa575848eda2d3966e872a9615427b/-/blob/src/vs/base/common/product.ts?L96
# for the original source of settings entries in the VS Code source code.
+ # See https://open-vsx.org/swagger-ui/index.html?urls.primaryName=VSCode%20Adapter#
+ # for OpenVSX Swagger API
{
service_url: "https://open-vsx.org/vscode/gallery",
item_url: "https://open-vsx.org/vscode/item",
- resource_url_template: "https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}",
+ resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}",
control_url: "",
nls_base_url: "",
publisher_url: ""
diff --git a/lib/web_ide/settings_sync.rb b/lib/web_ide/settings_sync.rb
index 4f4a2e352e7..829c36c5320 100644
--- a/lib/web_ide/settings_sync.rb
+++ b/lib/web_ide/settings_sync.rb
@@ -7,10 +7,13 @@ module WebIde
# "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_"
# This hash is used out in the wild, so we don't want to change it...
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/178491
- CURRENT_DEFAULT_SETTINGS_HASH = "2e0d3e8c1107f9ccc5ea"
-
- # The actual hash we calculate for default settings, but needs to be mapped to the CURRENT one for compatability
- DEFAULT_SETTINGS_HASH = "e36c431c0e2e1ee82c86"
+ HASH_CONVERSION = {
+ # 2e0d3e8c1107f9ccc5ea is the hash of "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_"
+ # e36c431c0e2e1ee82c86 is the hash of "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}"
+ # 55b10685e181429abe78 is the hash of "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}"
+ "e36c431c0e2e1ee82c86" => "2e0d3e8c1107f9ccc5ea",
+ "55b10685e181429abe78" => "2e0d3e8c1107f9ccc5ea"
+ }.freeze
def settings_context_hash(extension_marketplace_settings:)
return unless extension_marketplace_settings[:enabled]
@@ -29,9 +32,7 @@ module WebIde
private
def optionally_transform_hash(hash)
- return CURRENT_DEFAULT_SETTINGS_HASH if hash == DEFAULT_SETTINGS_HASH
-
- hash
+ HASH_CONVERSION.fetch(hash, hash)
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a9e0241107d..f4483e682b3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17008,6 +17008,9 @@ msgstr ""
msgid "Could not draw the lines for job relationships"
msgstr ""
+msgid "Could not fetch all changes. Try reloading the page."
+msgstr ""
+
msgid "Could not fetch policy because existing policy YAML is invalid"
msgstr ""
@@ -22136,6 +22139,9 @@ msgstr ""
msgid "DuoProTrial|You have successfully started a Duo Pro trial that will expire on %{exp_date}. To give members access to new GitLab Duo Pro features, %{assign_link_start}assign them%{assign_link_end} to GitLab Duo Pro seats."
msgstr ""
+msgid "Duplicate entries found for compliance controls for the requirement."
+msgstr ""
+
msgid "Duplicate packages"
msgstr ""
@@ -24534,6 +24540,9 @@ msgstr ""
msgid "Failed to add a resource link"
msgstr ""
+msgid "Failed to add compliance requirement control %{control_name}: %{error_message}"
+msgstr ""
+
msgid "Failed to add emoji. Please try again"
msgstr ""
@@ -37476,6 +37485,9 @@ msgstr ""
msgid "More options"
msgstr ""
+msgid "More than %{control_count} controls not allowed for a requirement."
+msgstr ""
+
msgid "More than %{number_commits_distance} commits different with %{default_branch}"
msgstr ""
@@ -38891,6 +38903,9 @@ msgstr ""
msgid "Not permitted to create compliance control"
msgstr ""
+msgid "Not permitted to create requirement"
+msgstr ""
+
msgid "Not permitted to destroy framework"
msgstr ""
diff --git a/spec/components/rapid_diffs/streaming_error_component_spec.rb b/spec/components/rapid_diffs/streaming_error_component_spec.rb
new file mode 100644
index 00000000000..918a7400662
--- /dev/null
+++ b/spec/components/rapid_diffs/streaming_error_component_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe RapidDiffs::StreamingErrorComponent, type: :component, feature_category: :code_review_workflow do
+ it "renders component with message" do
+ result = render_component('Foo')
+ expect(result).to have_css('streaming-error[message="Foo"]')
+ end
+
+ def render_component(message)
+ render_inline(described_class.new(message: message))
+ end
+end
diff --git a/spec/features/api/virtual_registries/packages/maven_spec.rb b/spec/features/api/virtual_registries/packages/maven_spec.rb
deleted file mode 100644
index af141cc7535..00000000000
--- a/spec/features/api/virtual_registries/packages/maven_spec.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Virtual Registries Packages Maven', :api, :js, feature_category: :virtual_registry do
- include_context 'file upload requests helpers'
- include_context 'with a server running the dependency proxy'
-
- let_it_be(:group) { create(:group) }
- let_it_be(:user) { create(:user, owner_of: group) }
- let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
- let_it_be(:registry) { create(:virtual_registries_packages_maven_registry, group: group) }
-
- let_it_be(:external_server) do
- handler = ->(env) do
- if env['REQUEST_PATH'] == '/file' # rubocop:disable RSpec/AvoidConditionalStatements -- This is a lambda for the external server
- [200, { 'Content-Type' => 'text/plain', 'ETag' => '"etag"' }, ['File contents']]
- else
- [404, {}, []]
- end
- end
-
- run_server(handler)
- end
-
- let_it_be(:upstream) do
- create(:virtual_registries_packages_maven_upstream, registry: registry)
- end
-
- let(:api_path) { "/virtual_registries/packages/maven/#{registry.id}/file" }
- let(:url) { capybara_url(api(api_path)) }
- let(:authorization) do
- ActionController::HttpAuthentication::Basic.encode_credentials(user.username, personal_access_token.token)
- end
-
- subject(:request) { HTTParty.get(url, headers: { authorization: authorization }) }
-
- before do
- upstream.update_column(:url, external_server.base_url) # avoids guard that rejects local urls
- stub_config(dependency_proxy: { enabled: true })
- allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
- end
-
- context 'with no cache entry' do
- it 'returns the file contents and create the cache entry' do
- expect { request }.to change { upstream.cache_entries.count }.by(1)
- end
- end
-
- context 'with a cache entry' do
- let_it_be_with_reload(:cache_entry) do
- create(
- :virtual_registries_packages_maven_cache_entry,
- :upstream_checked,
- upstream: upstream,
- relative_path: '/file',
- content_type: 'text/plain',
- upstream_etag: '"etag"'
- )
- end
-
- it 'returns the file contents from the cache' do
- expect(::Gitlab::HTTP).not_to receive(:head)
- expect { request }.not_to change { upstream.cache_entries.count }
- expect(request.headers[::API::VirtualRegistries::Packages::Maven::Endpoints::SHA1_CHECKSUM_HEADER])
- .to be_an_instance_of(String)
- expect(request.headers[::API::VirtualRegistries::Packages::Maven::Endpoints::MD5_CHECKSUM_HEADER])
- .to be_an_instance_of(String)
- end
-
- context 'with a stale cache entry' do
- before do
- cache_entry.update_column(:upstream_checked_at, 2.days.ago)
- end
-
- it 'returns the file contents and refresh the cache entry' do
- expect(::Gitlab::HTTP).to receive(:head).and_call_original
-
- expect { request }.to not_change { upstream.cache_entries.count }
- .and change { cache_entry.reload.upstream_checked_at }
- end
-
- context 'with a wrong etag' do
- before do
- cache_entry.update_column(:upstream_etag, 'wrong')
- end
-
- it 'returns the file contents and updates the cache entry' do
- expect(::Gitlab::HTTP).to receive(:head).and_call_original
-
- expect { request }.to not_change { upstream.cache_entries.count }
- .and change { cache_entry.reload.upstream_checked_at }
- .and change { cache_entry.reload.upstream_etag }.from('wrong').to('"etag"')
- end
- end
- end
- end
-end
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index c485fdb8175..0233a3a9049 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -5,6 +5,7 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
+import { createTestingPinia } from '@pinia/testing';
import api from '~/api';
import getMRCodequalityAndSecurityReports from 'ee_else_ce/diffs/components/graphql/get_mr_codequality_and_security_reports.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -26,7 +27,7 @@ import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
import eventHub from '~/diffs/event_hub';
import notesEventHub from '~/notes/event_hub';
-import { EVT_DISCUSSIONS_ASSIGNED } from '~/diffs/constants';
+import { EVT_DISCUSSIONS_ASSIGNED, FILE_BROWSER_VISIBLE } from '~/diffs/constants';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
@@ -38,7 +39,8 @@ import { stubPerformanceWebAPI } from 'helpers/performance';
import { getDiffFileMock } from 'jest/diffs/mock_data/diff_file';
import waitForPromises from 'helpers/wait_for_promises';
import { diffMetadata } from 'jest/diffs/mock_data/diff_metadata';
-import { pinia } from '~/pinia/instance';
+import { removeCookie, setCookie } from '~/lib/utils/common_utils';
+import { useFileBrowser } from '~/diffs/stores/file_browser';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import createDiffsStore from '../create_diffs_store';
import diffsMockData from '../mock_data/merge_request_diffs';
@@ -74,6 +76,7 @@ describe('diffs/components/app', () => {
baseConfig = {},
actions = {},
} = {}) => {
+ const pinia = createTestingPinia({ stubActions: false });
fakeApollo = createMockApollo([
[getMRCodequalityAndSecurityReports, codeQualityAndSastQueryHandlerSuccess],
]);
@@ -716,28 +719,26 @@ describe('diffs/components/app', () => {
});
});
- describe('setTreeDisplay', () => {
- afterEach(() => {
- localStorage.removeItem('mr_tree_show');
+ describe('file browser visibility', () => {
+ beforeEach(() => {
+ removeCookie(FILE_BROWSER_VISIBLE);
});
- it('calls setShowTreeList when only 1 file', () => {
+ it('hides files browser with only 1 file', async () => {
createComponent({
+ props: { shouldShow: true },
extendStore: ({ state }) => {
state.diffs.treeEntries = { 123: { type: 'blob', fileHash: '123' } };
},
});
- jest.spyOn(store, 'dispatch');
- wrapper.vm.setTreeDisplay();
+ await waitForPromises();
- expect(store.dispatch).toHaveBeenCalledWith('diffs/setShowTreeList', {
- showTreeList: false,
- saving: false,
- });
+ expect(useFileBrowser().setFileBrowserVisibility).toHaveBeenCalledWith(false);
});
- it('calls setShowTreeList with true when more than 1 file is in tree entries map', () => {
+ it('shows file browser with more than 1 file', async () => {
createComponent({
+ props: { shouldShow: true },
extendStore: ({ state }) => {
state.diffs.treeEntries = {
111: { type: 'blob', fileHash: '111', path: '111.js' },
@@ -745,37 +746,31 @@ describe('diffs/components/app', () => {
};
},
});
- jest.spyOn(store, 'dispatch');
+ await waitForPromises();
- wrapper.vm.setTreeDisplay();
-
- expect(store.dispatch).toHaveBeenCalledWith('diffs/setShowTreeList', {
- showTreeList: true,
- saving: false,
- });
+ expect(useFileBrowser().setFileBrowserVisibility).toHaveBeenCalledWith(true);
});
it.each`
- showTreeList
+ fileBrowserVisible
${true}
${false}
- `('calls setShowTreeList with localstorage $showTreeList', ({ showTreeList }) => {
- localStorage.setItem('mr_tree_show', showTreeList);
+ `(
+ 'sets browser visibility from cookie value: $fileBrowserVisible',
+ async ({ fileBrowserVisible }) => {
+ setCookie(FILE_BROWSER_VISIBLE, fileBrowserVisible);
- createComponent({
- extendStore: ({ state }) => {
- state.diffs.treeEntries['123'] = { sha: '123' };
- },
- });
- jest.spyOn(store, 'dispatch');
+ createComponent({
+ props: { shouldShow: true },
+ extendStore: ({ state }) => {
+ state.diffs.treeEntries['123'] = { sha: '123' };
+ },
+ });
+ await waitForPromises();
- wrapper.vm.setTreeDisplay();
-
- expect(store.dispatch).toHaveBeenCalledWith('diffs/setShowTreeList', {
- showTreeList,
- saving: false,
- });
- });
+ expect(useFileBrowser().setFileBrowserVisibility).toHaveBeenCalledWith(fileBrowserVisible);
+ },
+ );
});
describe('file-by-file', () => {
diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js
index b2bfc238c6a..3718512bf72 100644
--- a/spec/frontend/diffs/components/compare_versions_spec.js
+++ b/spec/frontend/diffs/components/compare_versions_spec.js
@@ -1,12 +1,15 @@
import { GlAnimatedSidebarIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import { createTestingPinia } from '@pinia/testing';
+import { PiniaVuePlugin } from 'pinia';
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
import store from '~/mr_notes/stores';
+import { useFileBrowser } from '~/diffs/stores/file_browser';
import diffsMockData from '../mock_data/merge_request_diffs';
jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores'));
@@ -18,6 +21,8 @@ beforeEach(() => {
setWindowLocation(TEST_HOST);
});
+Vue.use(PiniaVuePlugin);
+
describe('CompareVersions', () => {
let wrapper;
const targetBranchName = 'tmp-wine-dev';
@@ -28,6 +33,9 @@ describe('CompareVersions', () => {
store.state.diffs.commit = { ...store.state.diffs.commit, ...commitArgs };
}
+ const pinia = createTestingPinia();
+ // force Vue 2 mode by eager store creation
+ useFileBrowser();
wrapper = mount(CompareVersionsComponent, {
propsData: {
toggleFileTreeVisible: true,
@@ -36,6 +44,7 @@ describe('CompareVersions', () => {
mocks: {
$store: store,
},
+ pinia,
});
};
const findCompareSourceDropdown = () => wrapper.find('.mr-version-dropdown');
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index fab41854860..dd8ab33a6c6 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -1419,34 +1419,6 @@ describe('DiffsStoreActions', () => {
});
});
- describe('setShowTreeList', () => {
- it('commits toggle', () => {
- return testAction(
- diffActions.setShowTreeList,
- { showTreeList: true },
- {},
- [{ type: types.SET_SHOW_TREE_LIST, payload: true }],
- [],
- );
- });
-
- it('updates localStorage', () => {
- jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
-
- diffActions.setShowTreeList({ commit() {} }, { showTreeList: true });
-
- expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
- });
-
- it('does not update localStorage', () => {
- jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
-
- diffActions.setShowTreeList({ commit() {} }, { showTreeList: true, saving: false });
-
- expect(localStorage.setItem).not.toHaveBeenCalled();
- });
- });
-
describe('renderFileForDiscussionId', () => {
const rootState = {
notes: {
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index a10979aacaf..a5ad95c2791 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -994,16 +994,6 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('SET_SHOW_TREE_LIST', () => {
- it('sets showTreeList', () => {
- const state = createState();
-
- mutations[types.SET_SHOW_TREE_LIST](state, true);
-
- expect(state.showTreeList).toBe(true, 'Failed to toggle showTreeList to true');
- });
- });
-
describe('SET_CURRENT_DIFF_FILE', () => {
it('updates currentDiffFileId', () => {
const state = createState();
diff --git a/spec/frontend/diffs/stores/file_browser_spec.js b/spec/frontend/diffs/stores/file_browser_spec.js
new file mode 100644
index 00000000000..df225063cff
--- /dev/null
+++ b/spec/frontend/diffs/stores/file_browser_spec.js
@@ -0,0 +1,37 @@
+import { createPinia, setActivePinia } from 'pinia';
+import { useFileBrowser } from '~/diffs/stores/file_browser';
+import { getCookie, setCookie } from '~/lib/utils/common_utils';
+import { FILE_BROWSER_VISIBLE } from '~/diffs/constants';
+
+describe('FileBrowser store', () => {
+ beforeEach(() => {
+ setActivePinia(createPinia());
+ });
+
+ describe('browser visibility', () => {
+ beforeEach(() => {
+ window.document.cookie = '';
+ });
+
+ it('is visible by default', () => {
+ expect(useFileBrowser().fileBrowserVisible).toBe(true);
+ });
+
+ it('#setFileBrowserVisibility', () => {
+ useFileBrowser().setFileBrowserVisibility(false);
+ expect(useFileBrowser().fileBrowserVisible).toBe(false);
+ });
+
+ it('#toggleFileBrowserVisibility', () => {
+ useFileBrowser().toggleFileBrowserVisibility();
+ expect(useFileBrowser().fileBrowserVisible).toBe(false);
+ expect(getCookie(FILE_BROWSER_VISIBLE)).toBe('false');
+ });
+
+ it('#initFileBrowserVisibility', () => {
+ setCookie(FILE_BROWSER_VISIBLE, false);
+ useFileBrowser().initFileBrowserVisibility();
+ expect(useFileBrowser().fileBrowserVisible).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/diffs/stores/legacy_diffs/actions_spec.js b/spec/frontend/diffs/stores/legacy_diffs/actions_spec.js
index 67d9acd171e..b3df5129253 100644
--- a/spec/frontend/diffs/stores/legacy_diffs/actions_spec.js
+++ b/spec/frontend/diffs/stores/legacy_diffs/actions_spec.js
@@ -1457,34 +1457,6 @@ describe('legacyDiffs actions', () => {
});
});
- describe('setShowTreeList', () => {
- it('commits toggle', () => {
- return testAction(
- store.setShowTreeList,
- { showTreeList: true },
- {},
- [{ type: store[types.SET_SHOW_TREE_LIST], payload: true }],
- [],
- );
- });
-
- it('updates localStorage', () => {
- jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
-
- store.setShowTreeList({ showTreeList: true });
-
- expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
- });
-
- it('does not update localStorage', () => {
- jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
-
- store.setShowTreeList({ showTreeList: true, saving: false });
-
- expect(localStorage.setItem).not.toHaveBeenCalled();
- });
- });
-
describe('renderFileForDiscussionId', () => {
const notesState = {
discussions: [
diff --git a/spec/frontend/diffs/stores/legacy_diffs/mutations_spec.js b/spec/frontend/diffs/stores/legacy_diffs/mutations_spec.js
index 2c14bb43a50..7eef0b4b6de 100644
--- a/spec/frontend/diffs/stores/legacy_diffs/mutations_spec.js
+++ b/spec/frontend/diffs/stores/legacy_diffs/mutations_spec.js
@@ -982,14 +982,6 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('SET_SHOW_TREE_LIST', () => {
- it('sets showTreeList', () => {
- store[types.SET_SHOW_TREE_LIST](true);
-
- expect(store.showTreeList).toBe(true, 'Failed to toggle showTreeList to true');
- });
- });
-
describe('SET_CURRENT_DIFF_FILE', () => {
it('updates currentDiffFileId', () => {
store[types.SET_CURRENT_DIFF_FILE]('somefileid');
diff --git a/spec/frontend/rapid_diffs/app/app_spec.js b/spec/frontend/rapid_diffs/app/app_spec.js
index b5293880a9b..df04dd4d1be 100644
--- a/spec/frontend/rapid_diffs/app/app_spec.js
+++ b/spec/frontend/rapid_diffs/app/app_spec.js
@@ -7,6 +7,7 @@ import { DiffFileMounted } from '~/rapid_diffs/diff_file_mounted';
import { useDiffsList } from '~/rapid_diffs/stores/diffs_list';
import { pinia } from '~/pinia/instance';
import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser';
+import { StreamingError } from '~/rapid_diffs/streaming_error';
jest.mock('~/rapid_diffs/app/view_settings');
jest.mock('~/rapid_diffs/app/init_file_browser');
@@ -36,6 +37,7 @@ describe('Rapid Diffs App', () => {
expect(initFileBrowser).toHaveBeenCalled();
expect(window.customElements.get('diff-file')).toBe(DiffFile);
expect(window.customElements.get('diff-file-mounted')).toBe(DiffFileMounted);
+ expect(window.customElements.get('streaming-error')).toBe(StreamingError);
});
it('streams remaining diffs', () => {
diff --git a/spec/frontend/rapid_diffs/streaming_error_spec.js b/spec/frontend/rapid_diffs/streaming_error_spec.js
new file mode 100644
index 00000000000..2cfcac913a4
--- /dev/null
+++ b/spec/frontend/rapid_diffs/streaming_error_spec.js
@@ -0,0 +1,28 @@
+import { StreamingError } from '~/rapid_diffs/streaming_error';
+import { createAlert } from '~/alert';
+
+jest.mock('~/alert');
+
+describe('DiffFile Web Component', () => {
+ const html = `
+
+
+ `;
+
+ const renderComponent = () => {
+ document.body.innerHTML = html;
+ };
+
+ beforeAll(() => {
+ customElements.define('streaming-error', StreamingError);
+ });
+
+ it('creates an alert', () => {
+ const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ renderComponent();
+ expect(spy).toHaveBeenCalledWith('Failed to stream diffs: Foo');
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'Could not fetch all changes. Try reloading the page.',
+ });
+ });
+});
diff --git a/spec/frontend/work_items/components/design_management/design_preview/design_toolbar_spec.js b/spec/frontend/work_items/components/design_management/design_preview/design_toolbar_spec.js
index bb65cb2c3ef..e9923aeb40c 100644
--- a/spec/frontend/work_items/components/design_management/design_preview/design_toolbar_spec.js
+++ b/spec/frontend/work_items/components/design_management/design_preview/design_toolbar_spec.js
@@ -13,7 +13,6 @@ jest.mock('~/lib/utils/common_utils');
describe('DesignToolbar', () => {
let wrapper;
- const workItemTitle = 'Test title';
function createComponent({
isLoading = false,
@@ -22,7 +21,6 @@ describe('DesignToolbar', () => {
} = {}) {
wrapper = shallowMountExtended(DesignToolbar, {
propsData: {
- workItemTitle,
isLoading,
design,
isSidebarOpen: true,
@@ -57,7 +55,7 @@ describe('DesignToolbar', () => {
it('renders issue title and design filename', () => {
createComponent();
- expect(findDesignTitle()).toContain(workItemTitle);
+ expect(findDesignTitle()).toContain('My precious issue');
expect(findDesignTitle()).toContain(mockDesign.filename);
});
diff --git a/spec/lib/api/entities/virtual_registries/packages/maven/cache/entry_spec.rb b/spec/lib/api/entities/virtual_registries/packages/maven/cache/entry_spec.rb
deleted file mode 100644
index 361d1e5becd..00000000000
--- a/spec/lib/api/entities/virtual_registries/packages/maven/cache/entry_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::Entities::VirtualRegistries::Packages::Maven::Cache::Entry, feature_category: :virtual_registry do
- let(:cache_entry) { build_stubbed(:virtual_registries_packages_maven_cache_entry) }
-
- subject { described_class.new(cache_entry).as_json }
-
- it 'has the expected attributes' do
- is_expected.to include(:id, :group_id, :upstream_id, :upstream_checked_at, :created_at, :updated_at,
- :file_md5, :file_sha1, :size, :relative_path, :upstream_etag, :content_type)
- end
-end
diff --git a/spec/lib/api/entities/virtual_registries/packages/maven/registry_spec.rb b/spec/lib/api/entities/virtual_registries/packages/maven/registry_spec.rb
deleted file mode 100644
index f1cf3f8c012..00000000000
--- a/spec/lib/api/entities/virtual_registries/packages/maven/registry_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::Entities::VirtualRegistries::Packages::Maven::Registry, feature_category: :virtual_registry do
- let(:registry) { build_stubbed(:virtual_registries_packages_maven_registry) }
-
- subject { described_class.new(registry).as_json }
-
- it { is_expected.to include(:id, :group_id, :created_at, :updated_at) }
-end
diff --git a/spec/lib/api/entities/virtual_registries/packages/maven/upstream_spec.rb b/spec/lib/api/entities/virtual_registries/packages/maven/upstream_spec.rb
deleted file mode 100644
index 442c9091ef5..00000000000
--- a/spec/lib/api/entities/virtual_registries/packages/maven/upstream_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::Entities::VirtualRegistries::Packages::Maven::Upstream, feature_category: :virtual_registry do
- let(:upstream) { build_stubbed(:virtual_registries_packages_maven_upstream) }
-
- subject { described_class.new(upstream).as_json }
-
- it { is_expected.to include(:id, :group_id, :url, :cache_validity_hours, :created_at, :updated_at) }
-end
diff --git a/spec/lib/web_ide/extension_marketplace_spec.rb b/spec/lib/web_ide/extension_marketplace_spec.rb
index 95dd3e103ec..8f85e9090d9 100644
--- a/spec/lib/web_ide/extension_marketplace_spec.rb
+++ b/spec/lib/web_ide/extension_marketplace_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe WebIde::ExtensionMarketplace, feature_category: :web_ide do
item_url: 'https://open-vsx.org/vscode/item',
service_url: 'https://open-vsx.org/vscode/gallery',
resource_url_template:
- 'https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}'
+ 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}'
}
end
diff --git a/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb
index 5ce2522db48..38c2dfc051d 100644
--- a/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb
+++ b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe WebIde::Settings::ExtensionMarketplaceValidator, feature_category
let(:service_url) { "https://open-vsx.org/vscode/gallery" }
let(:item_url) { "https://open-vsx.org/vscode/item" }
- let(:resource_url_template) { "https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}" }
+ let(:resource_url_template) { "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" }
let(:vscode_extension_marketplace) do
{
service_url: service_url,
diff --git a/spec/lib/web_ide/settings/settings_initializer_spec.rb b/spec/lib/web_ide/settings/settings_initializer_spec.rb
index 34bdb4b2cc6..e22a16f55a4 100644
--- a/spec/lib/web_ide/settings/settings_initializer_spec.rb
+++ b/spec/lib/web_ide/settings/settings_initializer_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe WebIde::Settings::SettingsInitializer, feature_category: :web_ide
item_url: "https://open-vsx.org/vscode/item",
nls_base_url: "",
publisher_url: "",
- resource_url_template: 'https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}',
+ resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}',
service_url: "https://open-vsx.org/vscode/gallery"
},
vscode_extension_marketplace_metadata: {
diff --git a/spec/lib/web_ide/settings/settings_integration_spec.rb b/spec/lib/web_ide/settings/settings_integration_spec.rb
index 65ca3d43ebd..532d9ceb0b4 100644
--- a/spec/lib/web_ide/settings/settings_integration_spec.rb
+++ b/spec/lib/web_ide/settings/settings_integration_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab
{
service_url: "https://open-vsx.org/vscode/gallery",
item_url: "https://open-vsx.org/vscode/item",
- resource_url_template: 'https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}',
+ resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}',
control_url: "",
nls_base_url: "",
publisher_url: ""
@@ -72,7 +72,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab
stub_env("GITLAB_WEB_IDE_VSCODE_EXTENSION_MARKETPLACE",
'{"service_url":"https://OVERRIDE.org/vscode/gallery",' \
'"item_url":"https://OVERRIDE.org/vscode/item",' \
- '"resource_url_template":"https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}"}'
+ '"resource_url_template":"https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}"}'
)
end
@@ -81,7 +81,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab
{
service_url: "https://OVERRIDE.org/vscode/gallery",
item_url: "https://OVERRIDE.org/vscode/item",
- resource_url_template: "https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}"
+ resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}"
}
)
end
@@ -102,7 +102,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab
it "uses default value" do
expected_value = {
item_url: "https://open-vsx.org/vscode/item",
- resource_url_template: "https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}",
+ resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}",
service_url: "https://open-vsx.org/vscode/gallery",
control_url: "",
nls_base_url: "",
diff --git a/spec/lib/web_ide/settings_sync_spec.rb b/spec/lib/web_ide/settings_sync_spec.rb
index 15f49f0b1a7..71f8da8e551 100644
--- a/spec/lib/web_ide/settings_sync_spec.rb
+++ b/spec/lib/web_ide/settings_sync_spec.rb
@@ -25,6 +25,15 @@ RSpec.describe WebIde::SettingsSync, feature_category: :web_ide do
expectation: 'c6620244fe72864fa8d8'
},
"default vscode settings" => {
+ enabled: true,
+ vscode_settings: {
+ service_url: 'https://open-vsx.org/vscode/gallery',
+ item_url: 'https://open-vsx.org/vscode/item',
+ resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}'
+ },
+ expectation: '2e0d3e8c1107f9ccc5ea'
+ },
+ "default vscode settings (compatability)" => {
enabled: true,
vscode_settings: {
service_url: 'https://open-vsx.org/vscode/gallery',
diff --git a/spec/migrations/20240711035245_queue_backfill_root_namespace_cluster_agent_mappings_again_spec.rb b/spec/migrations/20240711035245_queue_backfill_root_namespace_cluster_agent_mappings_again_spec.rb
deleted file mode 100644
index 49324582425..00000000000
--- a/spec/migrations/20240711035245_queue_backfill_root_namespace_cluster_agent_mappings_again_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require_migration!
-
-RSpec.describe QueueBackfillRootNamespaceClusterAgentMappingsAgain, migration: :gitlab_main_cell,
- feature_category: :workspaces 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: :remote_development_agent_configs,
- column_name: :id,
- interval: described_class::DELAY_INTERVAL,
- batch_size: described_class::BATCH_SIZE,
- gitlab_schema: :gitlab_main_cell,
- sub_batch_size: described_class::SUB_BATCH_SIZE
- )
- }
- end
- end
-end
diff --git a/spec/requests/api/virtual_registries/packages/maven/cache/entries_spec.rb b/spec/requests/api/virtual_registries/packages/maven/cache/entries_spec.rb
deleted file mode 100644
index b2069b269f1..00000000000
--- a/spec/requests/api/virtual_registries/packages/maven/cache/entries_spec.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::VirtualRegistries::Packages::Maven::Cache::Entries, :aggregate_failures, feature_category: :virtual_registry do
- using RSpec::Parameterized::TableSyntax
- include_context 'for maven virtual registry api setup'
-
- describe 'GET /api/v4/virtual_registries/packages/maven/upstreams/:id/cache_entries' do
- let(:upstream_id) { upstream.id }
- let(:url) { "/virtual_registries/packages/maven/upstreams/#{upstream_id}/cache_entries" }
-
- let_it_be(:processing_cache_entry) do
- create(
- :virtual_registries_packages_maven_cache_entry,
- :processing,
- upstream: upstream,
- group: upstream.group,
- relative_path: cache_entry.relative_path
- )
- end
-
- subject(:api_request) { get api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- api_request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)).to contain_exactly(
- cache_entry
- .as_json
- .merge('id' => Base64.urlsafe_encode64("#{upstream.id} #{cache_entry.relative_path}"))
- .except('object_storage_key', 'file', 'file_store', 'status', 'file_final_path')
- )
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'with invalid upstream' do
- where(:upstream_id, :status) do
- non_existing_record_id | :not_found
- 'foo' | :bad_request
- '' | :bad_request
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'with a non-member user' do
- let_it_be(:user) { create(:user) }
-
- where(:group_access_level, :status) do
- 'PUBLIC' | :forbidden
- 'INTERNAL' | :forbidden
- 'PRIVATE' | :forbidden
- end
-
- with_them do
- before do
- group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
- end
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for authentication' do
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :ok
- :deploy_token | :header | :ok
- :job_token | :header | :ok
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for search param' do
- let(:url) { "#{super()}?search=#{search}" }
- let(:valid_search) { cache_entry.relative_path.slice(0, 5) }
-
- where(:search, :status) do
- ref(:valid_search) | :ok
- 'foo' | :empty
- '' | :ok
- nil | :ok
- end
-
- with_them do
- if params[:status] == :ok
- it_behaves_like 'successful response'
- else
- it 'returns an empty array' do
- api_request
-
- expect(json_response).to eq([])
- end
- end
- end
- end
- end
-
- describe 'DELETE /api/v4/virtual_registries/packages/maven/cache_entries/:id' do
- let(:id) { Base64.urlsafe_encode64("#{upstream.id} #{cache_entry.relative_path}") }
- let(:url) { "/virtual_registries/packages/maven/cache_entries/#{id}" }
-
- subject(:api_request) { delete api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- expect { api_request }.to change {
- VirtualRegistries::Packages::Maven::Cache::Entry.last.status
- }.from('default').to('pending_destruction')
-
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'for different user roles' do
- where(:user_role, :status) do
- :owner | :no_content
- :maintainer | :no_content
- :developer | :forbidden
- :reporter | :forbidden
- :guest | :forbidden
- end
-
- with_them do
- before do
- group.send(:"add_#{user_role}", user)
- end
-
- if params[:status] == :no_content
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'for authentication' do
- before_all do
- group.add_maintainer(user)
- end
-
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :no_content
- :deploy_token | :header | :forbidden
- :job_token | :header | :no_content
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- if params[:status] == :no_content
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'when error occurs' do
- before_all do
- group.add_maintainer(user)
- end
-
- before do
- allow_next_found_instance_of(cache_entry.class) do |instance|
- errors = ActiveModel::Errors.new(instance).tap { |e| e.add(:cache_entry, 'error message') }
- allow(instance).to receive_messages(mark_as_pending_destruction: false, errors: errors)
- end
- end
-
- it 'returns an error' do
- api_request
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response).to eq({ 'message' => { 'cache_entry' => ['error message'] } })
- end
- end
- end
-end
diff --git a/spec/requests/api/virtual_registries/packages/maven/endpoints_spec.rb b/spec/requests/api/virtual_registries/packages/maven/endpoints_spec.rb
deleted file mode 100644
index eb88a1c3e13..00000000000
--- a/spec/requests/api/virtual_registries/packages/maven/endpoints_spec.rb
+++ /dev/null
@@ -1,315 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::VirtualRegistries::Packages::Maven::Endpoints, :aggregate_failures, feature_category: :virtual_registry do
- using RSpec::Parameterized::TableSyntax
- include_context 'for maven virtual registry api setup'
-
- describe 'GET /api/v4/virtual_registries/packages/maven/:id/*path' do
- let_it_be(:path) { 'com/test/package/1.2.3/package-1.2.3.pom' }
-
- let(:url) { "/virtual_registries/packages/maven/#{registry.id}/#{path}" }
- let(:service_response) do
- ServiceResponse.success(
- payload: {
- action: :workhorse_upload_url,
- action_params: { url: upstream.url_for(path), upstream: upstream }
- }
- )
- end
-
- let(:service_double) do
- instance_double(::VirtualRegistries::Packages::Maven::HandleFileRequestService, execute: service_response)
- end
-
- before do
- allow(::VirtualRegistries::Packages::Maven::HandleFileRequestService)
- .to receive(:new)
- .with(registry: registry, current_user: user, params: { path: path })
- .and_return(service_double)
- end
-
- subject(:request) do
- get api(url), headers: headers
- end
-
- shared_examples 'returning the workhorse send_dependency response' do
- it 'returns a workhorse send_url response' do
- expect(::VirtualRegistries::Cache::EntryUploader).to receive(:workhorse_authorize).with(
- a_hash_including(
- use_final_store_path: true,
- final_store_path_config: { override_path: be_instance_of(String) }
- )
- ).and_call_original
-
- expect(Gitlab::Workhorse).to receive(:send_dependency).with(
- an_instance_of(Hash),
- an_instance_of(String),
- a_hash_including(
- allow_localhost: true,
- ssrf_filter: true,
- allowed_uris: ObjectStoreSettings.enabled_endpoint_uris
- )
- ).and_call_original
-
- request
-
- expect(response).to have_gitlab_http_status(:ok)
- 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('')
-
- send_data_type, send_data = workhorse_send_data
-
- expected_headers = upstream.headers.deep_stringify_keys.deep_transform_values do |value|
- [value]
- end
-
- expected_resp_headers = described_class::NO_BROWSER_EXECUTION_RESPONSE_HEADERS.deep_transform_values do |value|
- [value]
- end
-
- expected_upload_config = {
- 'Headers' => { described_class::UPSTREAM_GID_HEADER => [upstream.to_global_id.to_s] },
- 'AuthorizedUploadResponse' => a_kind_of(Hash)
- }
-
- 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 include(expected_upload_config)
- end
- end
-
- it_behaves_like 'maven virtual registry authenticated endpoint',
- success_shared_example_name: 'returning the workhorse send_dependency response' do
- let(:headers) { {} }
- end
-
- context 'with a valid user' do
- let(:headers) { { 'Private-Token' => personal_access_token.token } }
-
- context 'with successful handle request service responses' do
- let_it_be(:cache_entry) do
- create(
- :virtual_registries_packages_maven_cache_entry,
- content_type: 'text/xml',
- upstream: upstream,
- group_id: upstream.group_id,
- relative_path: "/#{path}"
- )
- end
-
- before do
- # reset the test stub to use the actual service
- allow(::VirtualRegistries::Packages::Maven::HandleFileRequestService).to receive(:new).and_call_original
- end
-
- context 'when the handle request service returns download_file' do
- it 'returns the workhorse send_url response' do
- request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.media_type).to eq(cache_entry.content_type)
- # this is a direct download from the file system, workhorse is not involved
- expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to be_nil
- end
- end
-
- context 'when the handle request service returns download_digest' do
- let(:path) { "#{super()}.sha1" }
-
- it 'returns the requested digest' do
- request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.media_type).to eq('text/plain')
- expect(response.body).to eq(cache_entry.file_sha1)
- end
- end
- end
-
- context 'with service response errors' do
- where(:reason, :expected_status) do
- :path_not_present | :bad_request
- :unauthorized | :unauthorized
- :no_upstreams | :bad_request
- :file_not_found_on_upstreams | :not_found
- :digest_not_found_in_cache_entries | :not_found
- :upstream_not_available | :bad_request
- :fips_unsupported_md5 | :bad_request
- end
-
- with_them do
- let(:service_response) do
- ServiceResponse.error(message: 'error', reason: reason)
- end
-
- it "returns a #{params[:expected_status]} response" do
- request
-
- expect(response).to have_gitlab_http_status(expected_status)
- expect(response.body).to include('error') unless expected_status == :unauthorized
- end
- end
- end
-
- context 'with a web browser' do
- described_class::MAJOR_BROWSERS.each do |browser|
- context "when accessing with a #{browser} browser" do
- before do
- allow_next_instance_of(::Browser) do |b|
- allow(b).to receive("#{browser}?").and_return(true)
- end
- end
-
- it 'returns a bad request response' do
- request
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(response.body).to include(described_class::WEB_BROWSER_ERROR_MESSAGE)
- end
- end
- 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
-
- context 'with a personal access token with only the read_virtual_registry scope' do
- let(:personal_access_token) { create(:personal_access_token, user: user, scopes: ['read_virtual_registry']) }
-
- let(:headers) { { 'Private-Token' => personal_access_token.token } }
-
- before do
- # read_virtual_registry is only available when the dependency proxy feature is enabled
- stub_config(dependency_proxy: { enabled: true })
- end
-
- it_behaves_like 'returning the workhorse send_dependency response'
- end
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- end
-
- context 'with no user' do
- let(:headers) { {} }
-
- it 'returns unauthorized with the www-authenticate header' do
- request
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- expect(response.headers[described_class::AUTHENTICATE_REALM_HEADER])
- .to eq(described_class::AUTHENTICATE_REALM_NAME)
- end
- end
-
- it_behaves_like 'maven virtual registry not authenticated user'
- end
-
- describe 'POST /api/v4/virtual_registries/packages/maven/:id/*path/upload' do
- include_context 'workhorse headers'
-
- 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(:additional_headers) do
- gid_header.merge(::Gitlab::Workhorse::SEND_DEPENDENCY_CONTENT_TYPE_HEADER => 'text/xml')
- end
-
- let(:headers) { workhorse_headers.merge(additional_headers) }
-
- let_it_be(:path) { 'com/test/package/1.2.3/package-1.2.3.pom' }
- let_it_be(:url) { "/virtual_registries/packages/maven/#{registry.id}/#{path}/upload" }
- let_it_be(:processing_cache_entries) do
- create(
- :virtual_registries_packages_maven_cache_entry,
- :processing,
- upstream: upstream,
- group: upstream.group,
- relative_path: "/#{path}"
- )
- end
-
- subject(:request) do
- workhorse_finalize(
- api(url),
- file_key: :file,
- headers: headers,
- params: {
- file: file_upload,
- 'file.md5' => 'd8e8fca2dc0f896fd7cb4cb0031ba249',
- 'file.sha1' => '4e1243bd22c66e76c2ba9eddc1f91394e57f9f83'
- },
- send_rewritten_field: true
- )
- end
-
- shared_examples 'returning successful response' do
- it 'accepts the upload', :freeze_time do
- expect { request }.to change { upstream.cache_entries.count }.by(1)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to eq('')
- expect(upstream.default_cache_entries.search_by_relative_path(path).last).to have_attributes(
- relative_path: "/#{path}",
- upstream_etag: nil,
- upstream_checked_at: Time.zone.now,
- file_sha1: kind_of(String),
- file_md5: kind_of(String)
- )
- end
- end
-
- it_behaves_like 'maven virtual registry 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
-
- context 'with a persistence error' do
- before do
- allow(::VirtualRegistries::Packages::Maven::Cache::Entry)
- .to receive(:create_or_update_by!).and_raise(ActiveRecord::RecordInvalid)
- end
-
- it_behaves_like 'returning response status', :bad_request
- end
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- end
-
- it_behaves_like 'maven virtual registry not authenticated user'
- end
-end
diff --git a/spec/requests/api/virtual_registries/packages/maven/registries_spec.rb b/spec/requests/api/virtual_registries/packages/maven/registries_spec.rb
deleted file mode 100644
index de71971e8db..00000000000
--- a/spec/requests/api/virtual_registries/packages/maven/registries_spec.rb
+++ /dev/null
@@ -1,332 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::VirtualRegistries::Packages::Maven::Registries, :aggregate_failures, feature_category: :virtual_registry do
- using RSpec::Parameterized::TableSyntax
- include_context 'for maven virtual registry api setup'
-
- describe 'GET /api/v4/groups/:id/-/virtual_registries/packages/maven/registries' do
- let(:group_id) { group.id }
- let(:url) { "/groups/#{group_id}/-/virtual_registries/packages/maven/registries" }
-
- subject(:api_request) { get api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- api_request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)).to contain_exactly(registry.as_json)
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'with valid group_id' do
- it_behaves_like 'successful response'
- end
-
- context 'with invalid group_id' do
- where(:group_id, :status) do
- non_existing_record_id | :not_found
- 'foo' | :not_found
- '' | :not_found
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'with a non member user' do
- let_it_be(:user) { create(:user) }
-
- where(:group_access_level, :status) do
- 'PUBLIC' | :forbidden
- 'INTERNAL' | :forbidden
- 'PRIVATE' | :not_found
- end
-
- with_them do
- before do
- group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
- end
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for authentication' do
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :ok
- :deploy_token | :header | :ok
- :job_token | :header | :ok
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- describe 'POST /api/v4/groups/:id/-/virtual_registries/packages/maven/registries' do
- let_it_be(:registry_class) { ::VirtualRegistries::Packages::Maven::Registry }
- let(:group_id) { group.id }
- let(:url) { "/groups/#{group_id}/-/virtual_registries/packages/maven/registries" }
-
- subject(:api_request) { post api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- expect { api_request }.to change { registry_class.count }.by(1)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Gitlab::Json.parse(response.body)).to eq(registry_class.last.as_json)
- end
- end
-
- context 'with valid group_id' do
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- where(:user_role, :status) do
- :owner | :created
- :maintainer | :created
- :developer | :forbidden
- :reporter | :forbidden
- :guest | :forbidden
- end
-
- with_them do
- before do
- registry_class.for_group(group).delete_all
- group.send(:"add_#{user_role}", user)
- end
-
- if params[:status] == :created
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'with existing registry' do
- before_all do
- group.add_maintainer(user)
- end
-
- it 'returns a bad request' do
- api_request
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response).to eq({ 'message' => { 'group' => ['has already been taken'] } })
- end
- end
-
- context 'for authentication' do
- before_all do
- group.add_maintainer(user)
- end
-
- before do
- registry_class.for_group(group).delete_all
- end
-
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :created
- :deploy_token | :header | :forbidden
- :job_token | :header | :created
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'with invalid group_id' do
- before_all do
- group.add_maintainer(user)
- end
-
- where(:group_id, :status) do
- non_existing_record_id | :not_found
- 'foo' | :not_found
- '' | :not_found
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'with subgroup' do
- let(:subgroup) { create(:group, parent: group, visibility_level: group.visibility_level) }
-
- let(:group_id) { subgroup.id }
-
- before_all do
- group.add_maintainer(user)
- end
-
- it 'returns a bad request because it is not a top level group' do
- api_request
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response).to eq({ 'message' => { 'group' => ['must be a top level Group'] } })
- end
- end
- end
-
- describe 'GET /api/v4/virtual_registries/packages/maven/registries/:id' do
- let(:registry_id) { registry.id }
- let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}" }
-
- subject(:api_request) { get api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- api_request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)).to eq(registry.as_json)
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'with valid registry_id' do
- it_behaves_like 'successful response'
- end
-
- context 'with invalid registry_id' do
- where(:registry_id, :status) do
- non_existing_record_id | :not_found
- 'foo' | :bad_request
- '' | :bad_request
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'with a non member user' do
- let_it_be(:user) { create(:user) }
-
- where(:group_access_level, :status) do
- 'PUBLIC' | :forbidden
- 'INTERNAL' | :forbidden
- 'PRIVATE' | :forbidden
- end
- with_them do
- before do
- group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
- end
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for authentication' do
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :ok
- :deploy_token | :header | :ok
- :job_token | :header | :ok
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- describe 'DELETE /api/v4/virtual_registries/packages/maven/registries/:id' do
- let(:registry_id) { registry.id }
- let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}" }
-
- subject(:api_request) { delete api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- expect { api_request }.to change { ::VirtualRegistries::Packages::Maven::Registry.count }.by(-1)
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'with valid registry_id' do
- where(:user_role, :status) do
- :owner | :no_content
- :maintainer | :no_content
- :developer | :forbidden
- :reporter | :forbidden
- :guest | :forbidden
- end
-
- with_them do
- before do
- group.send(:"add_#{user_role}", user)
- end
-
- if params[:status] == :no_content
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'with invalid registry_id' do
- where(:registry_id, :status) do
- non_existing_record_id | :not_found
- 'foo' | :bad_request
- '' | :not_found
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for authentication' do
- before_all do
- group.add_maintainer(user)
- end
-
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :no_content
- :deploy_token | :header | :forbidden
- :job_token | :header | :no_content
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-end
diff --git a/spec/requests/api/virtual_registries/packages/maven/upstreams_spec.rb b/spec/requests/api/virtual_registries/packages/maven/upstreams_spec.rb
deleted file mode 100644
index 6092da87e4a..00000000000
--- a/spec/requests/api/virtual_registries/packages/maven/upstreams_spec.rb
+++ /dev/null
@@ -1,401 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::VirtualRegistries::Packages::Maven::Upstreams, :aggregate_failures, feature_category: :virtual_registry do
- using RSpec::Parameterized::TableSyntax
- include_context 'for maven virtual registry api setup'
-
- describe 'GET /api/v4/virtual_registries/packages/maven/registries/:id/upstreams' do
- let(:registry_id) { registry.id }
- let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}/upstreams" }
-
- subject(:api_request) { get api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- api_request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)).to contain_exactly(registry.upstream.as_json)
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'with valid registry' do
- it_behaves_like 'successful response'
- end
-
- context 'with invalid registry' do
- where(:registry_id, :status) do
- non_existing_record_id | :not_found
- 'foo' | :bad_request
- '' | :bad_request
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'with a non member user' do
- let_it_be(:user) { create(:user) }
-
- where(:group_access_level, :status) do
- 'PUBLIC' | :forbidden
- 'INTERNAL' | :forbidden
- 'PRIVATE' | :forbidden
- end
-
- with_them do
- before do
- group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
- end
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for authentication' do
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :ok
- :deploy_token | :header | :ok
- :job_token | :header | :ok
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- describe 'POST /api/v4/virtual_registries/packages/maven/registries/:id/upstreams' do
- let(:registry_id) { registry.id }
- let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}/upstreams" }
- let(:params) { { url: 'http://example.com' } }
-
- subject(:api_request) { post api(url), headers: headers, params: params }
-
- shared_examples 'successful response' do
- let(:upstream_model) { ::VirtualRegistries::Packages::Maven::Upstream }
-
- it 'returns a successful response' do
- expect { api_request }.to change { upstream_model.count }.by(1)
- .and change { ::VirtualRegistries::Packages::Maven::RegistryUpstream.count }.by(1)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(Gitlab::Json.parse(response.body)).to eq(upstream_model.last.as_json)
- expect(upstream_model.last.cache_validity_hours).to eq(
- params[:cache_validity_hours] || upstream_model.new.cache_validity_hours
- )
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'with valid params' do
- where(:user_role, :status) do
- :owner | :created
- :maintainer | :created
- :developer | :forbidden
- :reporter | :forbidden
- :guest | :forbidden
- end
-
- with_them do
- before do
- registry.upstream&.destroy!
- group.send(:"add_#{user_role}", user)
- end
-
- if params[:status] == :created
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'with invalid registry' do
- where(:registry_id, :status) do
- non_existing_record_id | :not_found
- 'foo' | :bad_request
- '' | :not_found
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for params' do
- where(:params, :status) do
- { url: 'http://example.com', username: 'test', password: 'test', cache_validity_hours: 3 } | :created
- { url: 'http://example.com', username: 'test', password: 'test' } | :created
- { url: '', username: 'test', password: 'test' } | :bad_request
- { url: 'http://example.com', username: 'test' } | :bad_request
- {} | :bad_request
- end
-
- before do
- registry.upstream&.destroy!
- end
-
- before_all do
- group.add_maintainer(user)
- end
-
- with_them do
- if params[:status] == :created
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'with existing upstream' do
- before_all do
- group.add_maintainer(user)
- create(:virtual_registries_packages_maven_upstream, registry: registry)
- end
-
- it_behaves_like 'returning response status', :conflict
- end
-
- context 'for authentication' do
- before_all do
- group.add_maintainer(user)
- end
-
- before do
- registry.upstream&.destroy!
- end
-
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :created
- :deploy_token | :header | :forbidden
- :job_token | :header | :created
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- if params[:status] == :created
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
- end
-
- describe 'GET /api/v4/virtual_registries/packages/maven/upstreams/:id' do
- let(:url) { "/virtual_registries/packages/maven/upstreams/#{upstream.id}" }
-
- subject(:api_request) { get api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- api_request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)).to eq(registry.upstream.as_json)
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'with valid params' do
- it_behaves_like 'successful response'
- end
-
- context 'with a non member user' do
- let_it_be(:user) { create(:user) }
-
- where(:group_access_level, :status) do
- 'PUBLIC' | :forbidden
- 'INTERNAL' | :forbidden
- 'PRIVATE' | :forbidden
- end
-
- with_them do
- before do
- group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
- end
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
-
- context 'for authentication' do
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :ok
- :deploy_token | :header | :ok
- :job_token | :header | :ok
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- describe 'PATCH /api/v4/virtual_registries/packages/maven/upstreams/:id' do
- let(:url) { "/virtual_registries/packages/maven/upstreams/#{upstream.id}" }
-
- subject(:api_request) { patch api(url), params: params, headers: headers }
-
- context 'with valid params' do
- let(:params) { { url: 'http://example.com', username: 'test', password: 'test' } }
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- where(:user_role, :status) do
- :owner | :ok
- :maintainer | :ok
- :developer | :forbidden
- :reporter | :forbidden
- :guest | :forbidden
- end
-
- with_them do
- before do
- group.send(:"add_#{user_role}", user)
- end
-
- it_behaves_like 'returning response status', params[:status]
- end
-
- context 'for authentication' do
- before_all do
- group.add_maintainer(user)
- end
-
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :ok
- :deploy_token | :header | :forbidden
- :job_token | :header | :ok
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'for params' do
- before_all do
- group.add_maintainer(user)
- end
-
- let(:params) do
- { url: param_url, username: username, password: password, cache_validity_hours: cache_validity_hours }.compact
- end
-
- where(:param_url, :username, :password, :cache_validity_hours, :status) do
- nil | 'test' | 'test' | 3 | :ok
- 'http://example.com' | nil | 'test' | 3 | :ok
- 'http://example.com' | 'test' | nil | 3 | :ok
- 'http://example.com' | 'test' | 'test' | nil | :ok
- nil | nil | nil | 3 | :ok
- 'http://example.com' | 'test' | 'test' | 3 | :ok
- '' | 'test' | 'test' | 3 | :bad_request
- 'http://example.com' | '' | 'test' | 3 | :bad_request
- 'http://example.com' | 'test' | '' | 3 | :bad_request
- 'http://example.com' | 'test' | 'test' | -1 | :bad_request
- nil | nil | nil | nil | :bad_request
- end
-
- with_them do
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- describe 'DELETE /api/v4/virtual_registries/packages/maven/upstreams/:id' do
- let(:url) { "/virtual_registries/packages/maven/upstreams/#{upstream.id}" }
-
- subject(:api_request) { delete api(url), headers: headers }
-
- shared_examples 'successful response' do
- it 'returns a successful response' do
- expect { api_request }.to change { ::VirtualRegistries::Packages::Maven::Upstream.count }.by(-1)
- .and change { ::VirtualRegistries::Packages::Maven::RegistryUpstream.count }.by(-1)
- end
- end
-
- it { is_expected.to have_request_urgency(:low) }
-
- it_behaves_like 'disabled virtual_registry_maven feature flag'
- it_behaves_like 'maven virtual registry disabled dependency proxy'
- it_behaves_like 'maven virtual registry not authenticated user'
-
- context 'for different user roles' do
- where(:user_role, :status) do
- :owner | :no_content
- :maintainer | :no_content
- :developer | :forbidden
- :reporter | :forbidden
- :guest | :forbidden
- end
-
- with_them do
- before do
- group.send(:"add_#{user_role}", user)
- end
-
- if params[:status] == :no_content
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
-
- context 'for authentication' do
- before_all do
- group.add_maintainer(user)
- end
-
- where(:token, :sent_as, :status) do
- :personal_access_token | :header | :no_content
- :deploy_token | :header | :forbidden
- :job_token | :header | :no_content
- end
-
- with_them do
- let(:headers) { token_header(token) }
-
- if params[:status] == :no_content
- it_behaves_like 'successful response'
- else
- it_behaves_like 'returning response status', params[:status]
- end
- end
- end
- end
-end
diff --git a/spec/services/ml/create_experiment_service_spec.rb b/spec/services/ml/create_experiment_service_spec.rb
index 5fdca79cb33..2f3a15746f1 100644
--- a/spec/services/ml/create_experiment_service_spec.rb
+++ b/spec/services/ml/create_experiment_service_spec.rb
@@ -30,5 +30,16 @@ RSpec.describe ::Ml::CreateExperimentService, feature_category: :mlops do
expect(create_experiment).to be_error
end
end
+
+ context 'with invalid parameters' do
+ let(:name) { '' }
+
+ it 'returns validation errors' do
+ response = create_experiment
+
+ expect(response).to be_error
+ expect(response.message).to include("Name can't be blank")
+ end
+ end
end
end
diff --git a/spec/services/ml/create_model_version_service_spec.rb b/spec/services/ml/create_model_version_service_spec.rb
index 3fcaf548591..2ee9ec7ccd0 100644
--- a/spec/services/ml/create_model_version_service_spec.rb
+++ b/spec/services/ml/create_model_version_service_spec.rb
@@ -283,5 +283,35 @@ RSpec.describe ::Ml::CreateModelVersionService, feature_category: :mlops do
expect(service).to be_error.and have_attributes(message: ["Run with eid not found"])
end
end
+
+ context 'when a RecordNotUnique error occurs' do
+ let_it_be(:pg_error) { 'PG::UniqueViolation: ERROR: duplicate key value violates unique constraint' }
+
+ before do
+ allow_next_instance_of(::Ml::ModelVersion) do |model_version|
+ allow(model_version).to receive(:save).and_raise(
+ ActiveRecord::RecordNotUnique.new(pg_error)
+ )
+ end
+ end
+
+ it 'returns an error response with the exception message' do
+ expect(service).to be_error
+ expect(service.message).to include(pg_error)
+ expect(service.payload[:model_version]).to be_nil
+ end
+
+ it 'does not create model version or package' do
+ expect { service }.to not_change { Ml::ModelVersion.count }
+ .and not_change { Packages::MlModel::Package.count }
+ end
+
+ it 'does not track events or audit' do
+ service
+
+ expect(Gitlab::InternalEvents).not_to have_received(:track_event)
+ expect(Gitlab::Audit::Auditor).not_to have_received(:audit)
+ end
+ end
end
end
diff --git a/spec/support/shared_contexts/requests/api/maven_vreg_shared_context.rb b/spec/support/shared_contexts/requests/api/maven_vreg_shared_context.rb
deleted file mode 100644
index 6787cbc267e..00000000000
--- a/spec/support/shared_contexts/requests/api/maven_vreg_shared_context.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_context 'for maven virtual registry api setup' do
- include WorkhorseHelpers
- include HttpBasicAuthHelpers
-
- let_it_be(:group) { create(:group) }
- let_it_be_with_reload(:registry) { create(:virtual_registries_packages_maven_registry, group: group) }
- let_it_be(:upstream) { create(:virtual_registries_packages_maven_upstream, registry: registry) }
- let_it_be_with_reload(:cache_entry) do
- create(:virtual_registries_packages_maven_cache_entry, upstream: upstream)
- end
-
- let_it_be(:project) { create(:project, namespace: group) }
- let_it_be(:user) { create(:user, owner_of: project) }
- let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
- let_it_be(:deploy_token) do
- create(:deploy_token, :group, groups: [group], read_virtual_registry: true)
- end
-
- let(:personal_access_token) { create(:personal_access_token, user: user) }
- let(:headers) { token_header(:personal_access_token) }
-
- before do
- stub_config(dependency_proxy: { enabled: true }) # not enabled by default
- end
-
- def token_header(token)
- case token
- when :personal_access_token
- { 'PRIVATE-TOKEN' => personal_access_token.token }
- when :deploy_token
- { 'Deploy-Token' => deploy_token.token }
- when :job_token
- { 'Job-Token' => job.token }
- end
- end
-
- def token_basic_auth(token)
- case token
- when :personal_access_token
- user_basic_auth_header(user, personal_access_token)
- when :deploy_token
- deploy_token_basic_auth_header(deploy_token)
- when :job_token
- job_basic_auth_header(job)
- end
- end
-end
diff --git a/spec/support/shared_examples/requests/api/virtual_registries/maven_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/virtual_registries/maven_packages_shared_examples.rb
deleted file mode 100644
index 697ce59cb0d..00000000000
--- a/spec/support/shared_examples/requests/api/virtual_registries/maven_packages_shared_examples.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'disabled virtual_registry_maven feature flag' do
- before do
- stub_feature_flags(virtual_registry_maven: false)
- end
-
- it_behaves_like 'returning response status', :not_found
-end
-
-RSpec.shared_examples 'maven virtual registry disabled dependency proxy' do
- before do
- stub_config(dependency_proxy: { enabled: false })
- end
-
- it_behaves_like 'returning response status', :not_found
-end
-
-RSpec.shared_examples 'maven virtual registry not authenticated user' do
- let(:headers) { {} }
-
- it_behaves_like 'returning response status', :unauthorized
-end
-
-RSpec.shared_examples 'maven virtual registry 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