Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-03-07 09:09:20 +00:00
parent 0fc7ea6bc8
commit e09e832fe3
89 changed files with 520 additions and 2642 deletions

View File

@ -1 +1 @@
6bd8110489a44adeacff1897a757d8ea52bf4f87
ee1bf60a8952f6e9b4f3bf7627ac671ef1427cad

View File

@ -1 +1 @@
14.40.0
14.41.0

View File

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

View File

@ -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"
>
<gl-animated-sidebar-icon :is-on="showTreeList" />
<gl-animated-sidebar-icon :is-on="fileBrowserVisible" />
</gl-button>
<div v-if="commit">
{{ __('Viewing commit') }}

View File

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

View File

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

View File

@ -25,7 +25,6 @@ export default () => ({
diffViewType: INLINE_DIFF_VIEW_TYPE,
tree: [],
treeEntries: {},
showTreeList: true,
currentDiffFileId: '',
projectPath: '',
viewedDiffFileIds: {},

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,6 @@ export const useLegacyDiffs = defineStore('legacyDiffs', {
diffViewType: INLINE_DIFF_VIEW_TYPE,
tree: [],
treeEntries: {},
showTreeList: true,
currentDiffFileId: '',
projectPath: '',
viewedDiffFileIds: {},

View File

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

View File

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

View File

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

View File

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

View File

@ -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`;
},
},
};
</script>
@ -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 {
</gl-badge>
</div>
<div
:id="descriptionId"
v-safe-html:[$options.safeHtmlConfig]="feature.description"
class="gl-pt-3 gl-leading-20"
></div>
@ -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') }} <gl-icon name="arrow-right" />
</gl-button>

View File

@ -100,7 +100,6 @@ export default {
prevCurrentUserTodos: null,
maxScale: DEFAULT_MAX_SCALE,
workItemId: '',
workItemTitle: '',
isSidebarOpen: true,
};
},
@ -360,7 +359,6 @@ export default {
>
<div class="gl-relative gl-flex gl-grow gl-flex-col gl-overflow-hidden">
<design-toolbar
:work-item-title="workItemTitle"
:design="design"
:design-filename="$route.params.id"
:is-loading="isLoading"

View File

@ -31,10 +31,6 @@ export default {
GlTooltip: GlTooltipDirective,
},
props: {
workItemTitle: {
type: String,
required: true,
},
design: {
type: Object,
required: true,
@ -86,9 +82,9 @@ export default {
<gl-skeleton-loader v-if="isLoading" :lines="1" />
<h2 v-else class="gl-m-0 gl-flex gl-items-center gl-overflow-hidden gl-text-base">
<span class="gl-truncate gl-text-heading gl-no-underline">
{{ workItemTitle }}
{{ design.issue.title }}
</span>
<gl-icon name="chevron-right" class="gl-shrink-0" variant="disabled" />
<gl-icon name="chevron-right" class="gl-shrink-0" variant="subtle" />
<span class="gl-truncate gl-font-normal">{{ design.filename }}</span>
<imported-badge
v-if="design.imported"

View File

@ -0,0 +1 @@
%streaming-error{ message: @message.to_s }

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module RapidDiffs
class StreamingErrorComponent < ViewComponent::Base
def initialize(message:)
@message = message
end
end
end

View File

@ -13,9 +13,12 @@ module StreamDiffs
offset = { offset_index: params.permit(:offset)[:offset].to_i }
stream_diff_files(streaming_diff_options.merge(offset))
rescue ActionController::Live::ClientDisconnected
# Ignored
rescue StandardError => 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
5bd102fd3ff8463e40c4467317d3f146511ef683945e7c2cabaf8bff7abd3d04

View File

@ -0,0 +1 @@
166459c440ed6e868c66ddc3b79a5aae27b20d2bc21e489123b6676f8cd97841

View File

@ -0,0 +1 @@
dbdfbe9e0140762dcb606ee83ee5ac6ee6f2f0c4b9a60979f73394a9f81ec2e6

View File

@ -0,0 +1 @@
247a50b157e3bf43bc9a70ae74b1f2be18dfec57f9796cac6813a459a6f0cfee

View File

@ -0,0 +1 @@
0bd459ee0a6ad163f1e2e6ff856328f6898174736af667ec2c9f96ed18cffb1f

View File

@ -0,0 +1 @@
c13b43a5493f93febb1d54451741f1e39113a5b8b1d6959408ef4390eba92388

View File

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

View File

@ -4024,6 +4024,7 @@ Input type: `CreateComplianceRequirementInput`
| ---- | ---- | ----------- |
| <a id="mutationcreatecompliancerequirementclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatecompliancerequirementcomplianceframeworkid"></a>`complianceFrameworkId` | [`ComplianceManagementFrameworkID!`](#compliancemanagementframeworkid) | Global ID of the compliance framework of the new requirement. |
| <a id="mutationcreatecompliancerequirementcontrols"></a>`controls` | [`[ComplianceRequirementsControlInput!]`](#compliancerequirementscontrolinput) | Controls to add to the compliance requirement. |
| <a id="mutationcreatecompliancerequirementparams"></a>`params` | [`ComplianceRequirementInput!`](#compliancerequirementinput) | Parameters to update the compliance requirement with. |
#### Fields
@ -26427,7 +26428,7 @@ GPG signature for a signed commit.
| <a id="groupdescendantgroupscount"></a>`descendantGroupsCount` | [`Int!`](#int) | Count of direct descendant groups of this group. |
| <a id="groupdescription"></a>`description` | [`String`](#string) | Description of the namespace. |
| <a id="groupdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
| <a id="groupdora"></a>`dora` | [`Dora`](#dora) | Group's DORA metrics. |
| <a id="groupdora"></a>`dora` | [`GroupDora`](#groupdora) | Group's DORA metrics. |
| <a id="groupduofeaturesenabled"></a>`duoFeaturesEnabled` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Introduced** in GitLab 16.10. **Status**: Experiment. Indicates whether GitLab Duo features are enabled for the group. |
| <a id="groupemailsdisabled"></a>`emailsDisabled` | [`Boolean`](#boolean) | Indicates if a group has email notifications disabled. |
| <a id="groupemailsenabled"></a>`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.
| ---- | ---- | ----------- |
| <a id="groupdatatransferegressnodes"></a>`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 |
| ---- | ---- | ----------- |
| <a id="groupdorametricsenddate"></a>`endDate` | [`Date`](#date) | Date range to end at. Default is the current date. |
| <a id="groupdorametricsenvironmenttiers"></a>`environmentTiers` | [`[DeploymentTier!]`](#deploymenttier) | Deployment tiers of the environments to return. Defaults to `[PRODUCTION]`. |
| <a id="groupdorametricsinterval"></a>`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`. |
| <a id="groupdorametricsstartdate"></a>`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 |
| ---- | ---- | ----------- |
| <a id="groupdoraprojectscomplianceframeworkfilters"></a>`complianceFrameworkFilters` | [`ComplianceFrameworkFilters`](#complianceframeworkfilters) | Filters applied when selecting a compliance framework. |
| <a id="groupdoraprojectsenddate"></a>`endDate` | [`Date!`](#date) | Date range to end DORA lookup at. |
| <a id="groupdoraprojectshascodecoverage"></a>`hasCodeCoverage` | [`Boolean`](#boolean) | Returns only the projects which have code coverage. |
| <a id="groupdoraprojectshasvulnerabilities"></a>`hasVulnerabilities` | [`Boolean`](#boolean) | Returns only the projects which have vulnerabilities. |
| <a id="groupdoraprojectsids"></a>`ids` | [`[ID!]`](#id) | Filter projects by IDs. |
| <a id="groupdoraprojectsincludearchived"></a>`includeArchived` | [`Boolean`](#boolean) | Include also archived projects. |
| <a id="groupdoraprojectsincludesiblingprojects"></a>`includeSiblingProjects` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Introduced** in GitLab 17.2. **Status**: Experiment. Include also projects from parent group. |
| <a id="groupdoraprojectsincludesubgroups"></a>`includeSubgroups` | [`Boolean`](#boolean) | Include also subgroup projects. |
| <a id="groupdoraprojectsnotaimedfordeletion"></a>`notAimedForDeletion` | [`Boolean`](#boolean) | Include projects that are not aimed for deletion. |
| <a id="groupdoraprojectssbomcomponentid"></a>`sbomComponentId` | [`ID`](#id) | Return only the projects related to the specified SBOM component. |
| <a id="groupdoraprojectssearch"></a>`search` | [`String`](#string) | Search project with most similar names or paths. |
| <a id="groupdoraprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by the criteria. |
| <a id="groupdoraprojectsstartdate"></a>`startDate` | [`Date!`](#date) | Date range to start DORA lookup from. |
| <a id="groupdoraprojectswithissuesenabled"></a>`withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. |
| <a id="groupdoraprojectswithmergerequestsenabled"></a>`withMergeRequestsEnabled` | [`Boolean`](#boolean) | Return only projects with merge requests enabled. |
| <a id="groupdoraprojectswithnamespacedomainpages"></a>`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 |
| ---- | ---- | ----------- |
| <a id="compliancerequirementscontrolinputcontroltype"></a>`controlType` | [`String`](#string) | Type of the compliance control. |
| <a id="compliancerequirementscontrolinputexpression"></a>`expression` | [`String`](#string) | Expression of the compliance control. |
| <a id="compliancerequirementscontrolinputexpression"></a>`expression` | [`String!`](#string) | Expression of the compliance control. |
| <a id="compliancerequirementscontrolinputexternalurl"></a>`externalUrl` | [`String`](#string) | URL of the external control. |
| <a id="compliancerequirementscontrolinputname"></a>`name` | [`String`](#string) | New name for the compliance requirement control. |
| <a id="compliancerequirementscontrolinputname"></a>`name` | [`String!`](#string) | New name for the compliance requirement control. |
| <a id="compliancerequirementscontrolinputsecrettoken"></a>`secretToken` | [`String`](#string) | Secret token for an external control. |
### `ComplianceStandardsAdherenceInput`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,28 @@
import { StreamingError } from '~/rapid_diffs/streaming_error';
import { createAlert } from '~/alert';
jest.mock('~/alert');
describe('DiffFile Web Component', () => {
const html = `
<streaming-error message="Foo">
</streaming-error>
`;
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.',
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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