diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index fe126b38871..b1095a278d0 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-dbf0cf7484ba1265ee1cd5e3a49e04da933e931c
+d15b9c84faee3eb178e7c7d9360832f26d4107a2
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue
index f790c7b1430..0c3494ea812 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue
@@ -16,10 +16,6 @@ import {
export default {
name: 'DeleteModal',
- i18n: {
- DELETE_MODAL_CONTENT,
- DELETE_PACKAGES_MODAL_DESCRIPTION,
- },
components: {
GlLink,
GlModal,
@@ -38,15 +34,21 @@ export default {
},
computed: {
itemToBeDeleted() {
- if (this.itemsToBeDeleted.length === 1) {
- const [itemToBeDeleted] = this.itemsToBeDeleted;
- return itemToBeDeleted;
- }
- return null;
+ return this.itemsToBeDeleted.length === 1 ? this.itemsToBeDeleted[0] : null;
},
title() {
return this.itemToBeDeleted ? DELETE_MODAL_TITLE : DELETE_PACKAGES_MODAL_TITLE;
},
+ packageDescription() {
+ return this.showRequestForwardingContent
+ ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT
+ : DELETE_MODAL_CONTENT;
+ },
+ packagesDescription() {
+ return this.showRequestForwardingContent
+ ? DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT
+ : DELETE_PACKAGES_MODAL_DESCRIPTION;
+ },
packagesDeletePrimaryActionProps() {
let text = DELETE_PACKAGE_MODAL_PRIMARY_ACTION;
@@ -62,11 +64,6 @@ export default {
attributes: { variant: 'danger', category: 'primary' },
};
},
- requestForwardingContentMessage() {
- return this.itemToBeDeleted
- ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT
- : DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT;
- },
},
modal: {
cancelAction: {
@@ -95,24 +92,23 @@ export default {
@primary="$emit('confirm')"
@cancel="$emit('cancel')"
>
-
-
-
+
+
+
{{ content }}
-
-
-
-
{{ itemToBeDeleted.version }}
-
{{ itemToBeDeleted.name }}
-
+
+
+ {{ content }}
+
+
{{ itemsToBeDeleted.length }}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue
index 7ea19df7a6c..482249bc252 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue
@@ -6,7 +6,6 @@ import VersionRow from '~/packages_and_registries/package_registry/components/de
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
-import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
import {
CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
@@ -24,7 +23,6 @@ import getPackageVersionsQuery from '~/packages_and_registries/package_registry/
export default {
components: {
DeleteModal,
- DeletePackageModal,
GlAlert,
VersionRow,
PackagesListLoader,
@@ -47,6 +45,11 @@ export default {
required: false,
default: false,
},
+ isRequestForwardingEnabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
packageId: {
type: String,
required: true,
@@ -54,7 +57,6 @@ export default {
},
data() {
return {
- itemToBeDeleted: null,
itemsToBeDeleted: [],
packageVersions: {},
fetchPackageVersionsError: false,
@@ -79,6 +81,9 @@ export default {
},
},
computed: {
+ itemToBeDeleted() {
+ return this.itemsToBeDeleted.length === 1 ? this.itemsToBeDeleted[0] : null;
+ },
isListEmpty() {
return this.count === 0;
},
@@ -110,36 +115,31 @@ export default {
},
},
methods: {
- deleteItemConfirmation() {
- this.$emit('delete', [this.itemToBeDeleted]);
- this.track(DELETE_PACKAGE_VERSION_TRACKING_ACTION);
- this.itemToBeDeleted = null;
- },
- deleteItemCanceled() {
- this.track(CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION);
- this.itemToBeDeleted = null;
- },
deleteItemsCanceled() {
- this.track(CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ if (this.itemToBeDeleted) {
+ this.track(CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION);
+ } else {
+ this.track(CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ }
+
this.itemsToBeDeleted = [];
},
deleteItemsConfirmation() {
this.$emit('delete', this.itemsToBeDeleted);
- this.track(DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ if (this.itemToBeDeleted) {
+ this.track(DELETE_PACKAGE_VERSION_TRACKING_ACTION);
+ } else {
+ this.track(DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ }
this.itemsToBeDeleted = [];
},
- setItemToBeDeleted(item) {
- this.itemToBeDeleted = { ...item };
- this.track(REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION);
- },
setItemsToBeDeleted(items) {
- if (items.length === 1) {
- const [item] = items;
- this.setItemToBeDeleted(item);
- return;
- }
this.itemsToBeDeleted = items;
- this.track(REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ if (items.length === 1) {
+ this.track(REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION);
+ } else {
+ this.track(REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ }
this.$refs.deletePackagesModal.show();
},
fetchPreviousVersionsPage() {
@@ -196,21 +196,16 @@ export default {
:first="first"
:package-entity="item"
:selected="isSelected(item)"
- @delete="setItemToBeDeleted(item)"
+ @delete="setItemsToBeDeleted([item])"
@select="selectItem(item)"
/>
-
-
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
index ad5edcd7602..b4276d69ed6 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -136,7 +136,7 @@ export const DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION = s__(
'PackageRegistry|Yes, delete selected packages',
);
export const DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT = s__(
- 'PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete the package anyway? %{docLinkStart}What are the risks?%{docLinkEnd}',
+ 'PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete %{name} version %{version} anyway? %{docLinkStart}What are the risks?%{docLinkEnd}',
);
export const DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT = s__(
'PackageRegistry|Some of the selected package formats allow request forwarding. Deleting a package while request forwarding is enabled for the project can pose a security risk. Do you want to proceed with deleting the selected packages? %{docLinkStart}What are the risks?%{docLinkEnd}',
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
index 99864f7ad0c..984996b829a 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -1,3 +1,5 @@
+#import "~/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql"
+
query getPackageDetails($id: PackagesPackageID!) {
package(id: $id) {
id
@@ -23,6 +25,9 @@ query getPackageDetails($id: PackagesPackageID!) {
path
name
fullPath
+ group {
+ ...GroupPackageSettings
+ }
}
tags(first: 10) {
nodes {
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
index 0f1c63a04ad..6d4979ac785 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
@@ -2,6 +2,7 @@
import {
GlBadge,
GlButton,
+ GlLink,
GlModal,
GlModalDirective,
GlTooltipDirective,
@@ -37,6 +38,7 @@ import {
DELETE_PACKAGE_FILE_TRACKING_ACTION,
DELETE_PACKAGE_FILES_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
SHOW_DELETE_SUCCESS_ALERT,
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
@@ -44,6 +46,7 @@ import {
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
DELETE_PACKAGE_FILES_ERROR_MESSAGE,
DELETE_PACKAGE_FILES_SUCCESS_MESSAGE,
+ DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT,
DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION,
DELETE_MODAL_TITLE,
DELETE_MODAL_CONTENT,
@@ -64,6 +67,7 @@ export default {
GlButton,
GlEmptyState,
GlModal,
+ GlLink,
GlTab,
GlTabs,
GlSprintf,
@@ -124,6 +128,11 @@ export default {
},
},
computed: {
+ deleteModalContent() {
+ return this.isRequestForwardingEnabled
+ ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT
+ : this.deletePackageModalContent;
+ },
projectName() {
return this.packageEntity.project?.name;
},
@@ -170,6 +179,12 @@ export default {
showFiles() {
return this.packageType !== PACKAGE_TYPE_COMPOSER;
},
+ groupSettings() {
+ return this.packageEntity.project?.group?.packageSettings ?? {};
+ },
+ isRequestForwardingEnabled() {
+ return this.groupSettings[`${this.packageType.toLowerCase()}PackageRequestsForwarding`];
+ },
showMetadata() {
return [
PACKAGE_TYPE_COMPOSER,
@@ -291,6 +306,9 @@ export default {
),
otherVersionsTabTitle: s__('PackageRegistry|Other versions'),
},
+ links: {
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
+ },
modal: {
packageDeletePrimaryAction: {
text: s__('PackageRegistry|Permanently delete'),
@@ -398,6 +416,7 @@ export default {
:can-destroy="packageEntity.canDestroy"
:count="packageVersionsCount"
:is-mutation-loading="versionsMutationLoading"
+ :is-request-forwarding-enabled="isRequestForwardingEnabled"
:package-id="packageEntity.id"
@delete="deletePackages"
>
@@ -429,15 +448,23 @@ export default {
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)"
>
{{ $options.i18n.DELETE_MODAL_TITLE }}
-
-
- {{ packageEntity.version }}
-
+
+
+
+ {{
+ content
+ }}
+
-
- {{ packageEntity.name }}
-
-
+
+ {{ packageEntity.version }}
+
+
+
+ {{ packageEntity.name }}
+
+
+
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
index ec894586803..f8dcf1a5c9c 100644
--- a/app/assets/javascripts/pages/projects/blob/show/index.js
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -115,8 +115,8 @@ const initForkInfo = () => {
canSyncBranch,
aheadComparePath,
behindComparePath,
- canUserCreateMrInFork,
createMrPath,
+ viewMrPath,
} = forkEl.dataset;
return new Vue({
el: forkEl,
@@ -132,8 +132,8 @@ const initForkInfo = () => {
sourceDefaultBranch,
aheadComparePath,
behindComparePath,
- canUserCreateMrInFork,
createMrPath,
+ viewMrPath,
},
});
},
diff --git a/app/assets/javascripts/repository/components/fork_info.vue b/app/assets/javascripts/repository/components/fork_info.vue
index f3dbf98312e..1da445a7906 100644
--- a/app/assets/javascripts/repository/components/fork_info.vue
+++ b/app/assets/javascripts/repository/components/fork_info.vue
@@ -26,6 +26,7 @@ export const i18n = {
error: s__('ForksDivergence|Failed to fetch fork details. Try again later.'),
updateFork: s__('ForksDivergence|Update fork'),
createMergeRequest: s__('ForksDivergence|Create merge request'),
+ viewMergeRequest: s__('ForksDivergence|View merge request'),
successMessage: s__(
'ForksDivergence|Successfully fetched and merged from the upstream repository.',
),
@@ -121,10 +122,10 @@ export default {
required: false,
default: '',
},
- canUserCreateMrInFork: {
- type: Boolean,
+ viewMrPath: {
+ type: String,
required: false,
- default: false,
+ default: '',
},
},
data() {
@@ -203,7 +204,10 @@ export default {
);
},
hasCreateMrButton() {
- return this.canUserCreateMrInFork && this.ahead && this.createMrPath;
+ return this.ahead && this.createMrPath;
+ },
+ hasViewMrButton() {
+ return this.viewMrPath;
},
forkDivergenceMessage() {
if (!this.forkDetails) {
@@ -319,6 +323,14 @@ export default {
>
{{ $options.i18n.createMergeRequest }}
+
+ {{ $options.i18n.viewMergeRequest }}
+
_('No access'),
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index b98fdba44ec..382f861a802 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -10,7 +10,6 @@ module Ci
include Presentable
include ChronicDurationAttribute
include Gitlab::Utils::StrongMemoize
- include IgnorableColumns
include SafelyChangeColumnDefault
self.table_name = 'p_ci_builds_metadata'
@@ -19,8 +18,6 @@ module Ci
partitionable scope: :build
- ignore_column :runner_machine_id, remove_with: '16.0', remove_after: '2023-04-22'
-
belongs_to :build, class_name: 'CommitStatus'
belongs_to :project
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 1f8e003b09a..d84ba880e71 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -36,7 +36,20 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
condition(:request_access_enabled) { @subject.request_access_enabled }
condition(:create_projects_disabled, scope: :subject) do
- @subject.project_creation_level == ::Gitlab::Access::NO_ONE_PROJECT_ACCESS
+ next true if @user.nil?
+
+ visibility_levels = if @user.can_admin_all_resources?
+ # admin can create projects even with restricted visibility levels
+ Gitlab::VisibilityLevel.values
+ else
+ Gitlab::VisibilityLevel.allowed_levels
+ end
+
+ allowed_visibility_levels = visibility_levels.select do |level|
+ Project.new(namespace: @subject).visibility_level_allowed?(level)
+ end
+
+ @subject.project_creation_level == ::Gitlab::Access::NO_ONE_PROJECT_ACCESS || allowed_visibility_levels.empty?
end
condition(:developer_maintainer_access, scope: :subject) do
diff --git a/db/post_migrate/20230314144640_reschedule_migration_for_links.rb b/db/post_migrate/20230314144640_reschedule_migration_for_links.rb
index 311420f4f37..26d2f5b1d2c 100644
--- a/db/post_migrate/20230314144640_reschedule_migration_for_links.rb
+++ b/db/post_migrate/20230314144640_reschedule_migration_for_links.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-# rubocop: disable BackgroundMigration/MissingDictionaryFile
-
class RescheduleMigrationForLinks < Gitlab::Database::Migration[2.1]
MIGRATION = 'MigrateLinksForVulnerabilityFindings'
DELAY_INTERVAL = 2.minutes
@@ -13,20 +11,10 @@ class RescheduleMigrationForLinks < Gitlab::Database::Migration[2.1]
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
- delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
-
- queue_batched_background_migration(
- MIGRATION,
- :vulnerability_occurrences,
- :id,
- job_interval: DELAY_INTERVAL,
- batch_size: BATCH_SIZE,
- sub_batch_size: SUB_BATCH_SIZE
- )
+ # no-op as it is rescheduled via db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb
end
def down
- delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
+ # no-op as it is rescheduled via db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb
end
end
-# rubocop: enable BackgroundMigration/MissingDictionaryFile
diff --git a/db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb b/db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb
new file mode 100644
index 00000000000..9c6213c60ee
--- /dev/null
+++ b/db/post_migrate/20230412141541_reschedule_links_avoiding_duplication.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+# rubocop: disable BackgroundMigration/MissingDictionaryFile
+
+class RescheduleLinksAvoidingDuplication < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'MigrateLinksForVulnerabilityFindings'
+ DELAY_INTERVAL = 2.minutes
+ SUB_BATCH_SIZE = 500
+ BATCH_SIZE = 10000
+
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
+
+ queue_batched_background_migration(
+ MIGRATION,
+ :vulnerability_occurrences,
+ :id,
+ job_interval: DELAY_INTERVAL,
+ batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
+ end
+end
+# rubocop: enable BackgroundMigration/MissingDictionaryFile
diff --git a/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb b/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb
index 2cc433de605..fda776f3bc5 100644
--- a/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb
+++ b/db/post_migrate/20230414075119_add_namespaces_by_top_level_namespace_index.rb
@@ -1,15 +1,11 @@
# frozen_string_literal: true
class AddNamespacesByTopLevelNamespaceIndex < Gitlab::Database::Migration[2.1]
- disable_ddl_transaction!
-
- INDEX_NAME = 'index_on_namespaces_namespaces_by_top_level_namespace'
-
def up
- prepare_async_index :namespaces, '((traversal_ids[1]), type, id)', name: INDEX_NAME
+ # no-op: re-implemented in AddNamespacesByTopLevelNamespaceIndexV2
end
def down
- unprepare_async_index_by_name :ci_builds, INDEX_NAME
+ # no-op
end
end
diff --git a/db/post_migrate/20230419121943_add_namespaces_by_top_level_namespace_index_v2.rb b/db/post_migrate/20230419121943_add_namespaces_by_top_level_namespace_index_v2.rb
new file mode 100644
index 00000000000..5433313407d
--- /dev/null
+++ b/db/post_migrate/20230419121943_add_namespaces_by_top_level_namespace_index_v2.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddNamespacesByTopLevelNamespaceIndexV2 < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_on_namespaces_namespaces_by_top_level_namespace'
+
+ def up
+ unprepare_async_index_by_name :namespaces, INDEX_NAME
+ prepare_async_index :namespaces, '(traversal_ids[1]), type, id', name: INDEX_NAME
+ end
+
+ def down
+ unprepare_async_index_by_name :namespaces, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20230412141541 b/db/schema_migrations/20230412141541
new file mode 100644
index 00000000000..5ad6e38d98b
--- /dev/null
+++ b/db/schema_migrations/20230412141541
@@ -0,0 +1 @@
+777065413e8eb5605037885fb1a38c74b1f464733afb2718380f081edb9ab8a8
\ No newline at end of file
diff --git a/db/schema_migrations/20230419121943 b/db/schema_migrations/20230419121943
new file mode 100644
index 00000000000..255098a5411
--- /dev/null
+++ b/db/schema_migrations/20230419121943
@@ -0,0 +1 @@
+101dac198ed6204b5b74f809765d2a7f1907907fdfcfbe579989b8fcf61610c0
\ No newline at end of file
diff --git a/doc/api/import.md b/doc/api/import.md
index 4aeb5277b4a..1de2a92b5ea 100644
--- a/doc/api/import.md
+++ b/doc/api/import.md
@@ -8,12 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Use the Import API to import repositories from GitHub or Bitbucket Server.
-Related APIs include:
-
-- [Group migration by direct transfer API](bulk_imports.md).
-- [Group import and export API](group_import_export.md).
-- [Project import and export API](project_import_export.md).
-
## Prerequisites
For information on prerequisites for using the Import API, see:
@@ -214,3 +208,9 @@ curl --request POST \
For information on automating user, group, and project import API calls, see
[Automate group and project import](../user/project/import/index.md#automate-group-and-project-import).
+
+## Related topics
+
+- [Group migration by direct transfer API](bulk_imports.md).
+- [Group import and export API](group_import_export.md).
+- [Project import and export API](project_import_export.md).
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
index 34e043a30d6..0652b88617b 100644
--- a/doc/development/api_styleguide.md
+++ b/doc/development/api_styleguide.md
@@ -57,6 +57,59 @@ get do
end
```
+## Breaking changes
+
+We must not make breaking changes to our REST API v4, even in major GitLab releases.
+
+Our REST API maintains its own versioning independent of GitLab versioning.
+The current REST API version is `4`. [We commit to follow semantic versioning for our REST API](../api/rest/index.md#compatibility-guidelines),
+which means we cannot make breaking changes until a major version change (most likely, `5`).
+
+Because version `5` is not scheduled, we allow rare [exceptions](#exceptions).
+
+### Accommodating backward compatibility instead of breaking changes
+
+Backward compatibility can often be accommodated in the API by continuing to adapt a changed feature to
+the old API schema. For example, our REST API
+[exposes](https://gitlab.com/gitlab-org/gitlab/-/blob/c104f6b8/lib/api/entities/merge_request_basic.rb#L43-47) both
+`work_in_progress` and `draft` fields.
+
+### Exceptions
+
+The exception is only when:
+
+- A feature must be removed in a major GitLab release.
+- Backward compatibility cannot be maintained
+ [in any form](#accommodating-backward-compatibility-instead-of-breaking-changes).
+
+This exception should be rare.
+
+Even in this exception, rather than removing a field or argument, we must always do the following:
+
+- Return an empty response for a field (for example, `"null"` or `[]`).
+- Turn an argument into a no-op.
+
+## What is a breaking change
+
+Some examples of breaking changes are:
+
+- Removing or renaming fields, arguments, or enum values.
+- Removing endpoints.
+- Adding new redirects (not all clients follow redirects).
+- Changing the type of fields in the response (for example, from `String` to `Integer`).
+- Adding a new **required** argument.
+- Changing authentication, authorization, or other header requirements.
+- Changing [any status code](../api/rest/index.md#status-codes) other than `500`.
+
+## What is not a breaking change
+
+Some examples of non-breaking changes:
+
+- Any additive change, such as adding endpoints, non-required arguments, fields, or enum values.
+- Changes to error messages.
+- Changes from a `500` status code to [any supported status code](../api/rest/index.md#status-codes) (this is a bugfix).
+- Changes to the order of fields returned in a response.
+
## Declared parameters
Grape allows you to access only the parameters that have been declared by your
diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md
index 3d5e6aae25a..98a497518cb 100644
--- a/doc/development/integrations/jira_connect.md
+++ b/doc/development/integrations/jira_connect.md
@@ -104,7 +104,7 @@ The following steps describe setting up an environment to test the GitLab OAuth
- Confidential: **No**
1. Copy the Application ID.
1. Go to **Admin > Settings > General**.
-1. Scroll down and expand the GitLab for Jira App section.
+1. Expand **GitLab for Jira App**.
1. Go to [gitpod.io/variables](https://gitpod.io/variables).
1. Paste the Application ID into the **Jira Connect Application ID** field and select **Save changes**.
diff --git a/doc/integration/jira/connect-app.md b/doc/integration/jira/connect-app.md
index fa7d48ba44d..c1958f150e9 100644
--- a/doc/integration/jira/connect-app.md
+++ b/doc/integration/jira/connect-app.md
@@ -10,7 +10,7 @@ With the [GitLab for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/
you can integrate GitLab and Jira Cloud.
If you use GitLab.com and Jira Cloud, you can install the GitLab for Jira Cloud app.
-If you do not use both of these environments, use the [Jira DVCS Connector](dvcs/index.md) instead
+If you do not use both of these environments, use the [Jira DVCS connector](dvcs/index.md) instead
or [install the GitLab for Jira Cloud app manually](#install-the-gitlab-for-jira-cloud-app-manually).
You should use the GitLab for Jira Cloud app because data is synchronized
in real time. The Jira DVCS connector updates data only once per hour.
@@ -79,7 +79,7 @@ To create an OAuth application:
1. Select **Save application**.
1. Copy the **Application ID** value.
1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`).
-1. Expand the **GitLab for Jira App** section.
+1. Expand **GitLab for Jira App**.
1. Paste the **Application ID** value into **Jira Connect Application ID**.
1. Select **Save changes**.
1. Optional. Enable the `jira_connect_oauth` [feature flag](../../administration/feature_flags.md) to avoid [authentication problems in some browsers](#browser-displays-a-sign-in-message-when-already-signed-in).
@@ -113,7 +113,7 @@ To set up your self-managed instance for the GitLab for Jira Cloud app in GitLab
1. On the top bar, select **Main menu > Admin**.
1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`).
-1. Expand the **GitLab for Jira App** section.
+1. Expand **GitLab for Jira App**.
1. In **Jira Connect Proxy URL**, enter `https://gitlab.com`.
1. Select **Save changes**.
@@ -207,7 +207,7 @@ To configure your GitLab instance to serve as a proxy:
1. On the top bar, select **Main menu > Admin**.
1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`).
-1. Expand the **GitLab for Jira App** section.
+1. Expand **GitLab for Jira App**.
1. Select **Enable public key storage**.
1. Select **Save changes**.
1. [Install the GitLab for Jira Cloud app manually](#install-the-gitlab-for-jira-cloud-app-manually).
@@ -270,7 +270,7 @@ You might get an error if you have installed the GitLab for Jira Cloud app from
1. On the top bar, select **Main menu > Admin**.
1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`).
- 1. Expand the **GitLab for Jira App** section.
+ 1. Expand **GitLab for Jira App**.
1. Clear the **Jira Connect Proxy URL** text box.
1. Select **Save changes**.
@@ -296,7 +296,7 @@ To resolve this issue on GitLab self-managed, follow one of the solutions below,
- In GitLab 14.9 and later:
- Contact the [Jira Software Cloud support](https://support.atlassian.com/jira-software-cloud/) and ask to trigger a new installed lifecycle event for the GitLab for Jira Cloud app in your namespace.
- In all GitLab versions:
- - Re-install the GitLab for Jira Cloud app. This might remove all already synced development panel data.
+ - Re-install the GitLab for Jira Cloud app. This method might remove all synced data from the Jira development panel.
### `Failed to update GitLab version` error when setting up the GitLab for Jira Cloud app for self-managed instances
diff --git a/doc/integration/jira/dvcs/index.md b/doc/integration/jira/dvcs/index.md
index 759fea41ba5..79b3effaf3f 100644
--- a/doc/integration/jira/dvcs/index.md
+++ b/doc/integration/jira/dvcs/index.md
@@ -81,7 +81,7 @@ or with a [user-owned application](../../oauth_provider.md#create-a-user-owned-a
1. In the **Redirect URI** text box, enter the generated **Redirect URL** from
[linking GitLab accounts](https://confluence.atlassian.com/adminjiraserver/linking-gitlab-accounts-1027142272.html).
1. In **Scopes**, select `api` and clear any other checkboxes.
- The DVCS connector requires a **write-enabled** `api` scope to automatically create and manage required webhooks.
+ The Jira DVCS connector requires a **write-enabled** `api` scope to automatically create and manage required webhooks.
1. Select **Submit**.
1. Copy the **Application ID** and **Secret** values.
You need these values to configure Jira.
diff --git a/doc/integration/jira/dvcs/troubleshooting.md b/doc/integration/jira/dvcs/troubleshooting.md
index 6a2e1e052ba..04430fe5509 100644
--- a/doc/integration/jira/dvcs/troubleshooting.md
+++ b/doc/integration/jira/dvcs/troubleshooting.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Troubleshooting Jira DVCS connector **(FREE)**
-Refer to the items in this section if you're having problems with your DVCS connector.
+Refer to the items in this section if you're having problems with your Jira DVCS connector.
## Jira cannot access GitLab server
@@ -32,12 +32,12 @@ Problems with SSL and TLS can cause this error message:
Error obtaining access token. Cannot access https://gitlab.example.com from Jira.
```
-- The [GitLab Jira integration](../index.md) requires
+- The [Jira integration](../index.md) requires
GitLab to connect to Jira. Any TLS issues that arise from a private certificate
authority or self-signed certificate are resolved
[on the GitLab server](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates),
as GitLab is the TLS client.
-- The Jira Development panel integration requires Jira to connect to GitLab, which
+- The Jira development panel requires Jira to connect to GitLab, which
causes Jira to be the TLS client. If your GitLab server's certificate is not
issued by a public certificate authority, add the appropriate certificate
(such as your organization's root certificate) to the Java Truststore on Jira Server.
@@ -125,7 +125,7 @@ For more information, see the
## `Sync Failed` error when refreshing repository data
-If you get a `Sync Failed` error in Jira when [refreshing repository data](index.md#refresh-data-imported-to-jira) for specific projects, check your DVCS connector logs. Look for errors that occur when executing requests to API resources in GitLab. For example:
+If you get a `Sync Failed` error in Jira when [refreshing repository data](index.md#refresh-data-imported-to-jira) for specific projects, check your Jira DVCS connector logs. Look for errors that occur when executing requests to API resources in GitLab. For example:
```plaintext
Failed to execute request [https://gitlab.com/api/v4/projects/:id/merge_requests?page=1&per_page=100 GET https://gitlab.com/api/v4/projects/:id/merge_requests?page=1&per_page=100 returned a response status of 403 Forbidden] errors:
diff --git a/doc/integration/jira/index.md b/doc/integration/jira/index.md
index 41d6cda4c84..5b60994e94c 100644
--- a/doc/integration/jira/index.md
+++ b/doc/integration/jira/index.md
@@ -10,7 +10,7 @@ If your organization uses [Jira](https://www.atlassian.com/software/jira) issues
you can [migrate your issues from Jira](../../user/project/import/jira.md) and work
exclusively in GitLab. However, if you'd like to continue to use Jira, you can
integrate it with GitLab. GitLab offers two types of Jira integrations, and you
-can use one or both depending on the capabilities you need. We recommend you enable both.
+can use one or both depending on the capabilities you need.
## Compare integrations
@@ -19,29 +19,25 @@ in your GitLab project with any of your projects in Jira.
### Jira integration
-This integration connects one or more GitLab projects to a Jira instance. You can host
+The Jira integration connects one or more GitLab projects to a Jira instance. You can host
the Jira instance yourself or in [Atlassian Cloud](https://www.atlassian.com/migration/assess/why-cloud).
The supported Jira versions are `6.x`, `7.x`, `8.x`, and `9.x`.
-
-For an overview, see [Agile Management - GitLab-Jira Basic Integration](https://www.youtube.com/watch?v=fWvwkx5_00E&feature=youtu.be).
+For more information, see [Jira integration](configure.md).
-To set up the integration, [configure the settings](configure.md) in GitLab.
+### Jira development panel
-### Jira development panel integration
+The [Jira development panel](development_panel.md) connects all GitLab projects in a group or personal namespace.
+When you configure the Jira development panel, you can
+[view GitLab activity for an issue](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/)
+including related branches, commits, and merge requests.
-The [Jira development panel integration](development_panel.md)
-connects all GitLab projects under a group or personal namespace. When configured,
-relevant GitLab information, including related branches, commits, and merge requests,
-displays in the [development panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/).
-
-To set up the Jira development panel integration, use the GitLab for Jira Cloud app
-or the Jira DVCS (distributed version control system) connector,
-[depending on your installation](development_panel.md#configure-the-panel).
+To set up the Jira development panel, use the GitLab for Jira Cloud app
+or the Jira DVCS connector. For more information, see [Configure the panel](development_panel.md#configure-the-panel).
### Direct feature comparison
-| Capability | Jira integration | Jira development panel integration |
+| Capability | Jira integration | Jira development panel |
|-|-|-|
| Mention a Jira issue ID in a GitLab commit or merge request, and a link to the Jira issue is created. | Yes. | No. |
| Mention a Jira issue ID in GitLab and the Jira issue shows the GitLab issue or merge request. | Yes. A Jira comment with the GitLab issue or MR title links to GitLab. The first mention is also added to the Jira issue under **Web links**. | Yes, in the issue's [development panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/). |
@@ -72,8 +68,8 @@ All Jira integrations share data with Jira to make it visible outside of GitLab.
If you integrate a private GitLab project with Jira, the private data is
shared with users who have access to your Jira project.
-The [**Jira project integration**](#jira-integration) posts GitLab data in the form of comments in Jira issues.
-The GitLab for Jira Cloud app and Jira DVCS connector share this data through the [**Jira Development Panel**](development_panel.md).
+The [Jira integration](#jira-integration) posts GitLab data in the form of comments in Jira issues.
+The GitLab for Jira Cloud app and Jira DVCS connector share this data through the [Jira development panel](development_panel.md).
This method provides more fine-grained access control because access can be restricted to certain user groups or roles.
## Third-party Jira integrations
diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb
index db6206a240c..25c97932e31 100644
--- a/lib/api/concerns/packages/debian_package_endpoints.rb
+++ b/lib/api/concerns/packages/debian_package_endpoints.rb
@@ -100,7 +100,7 @@ module API
end
authenticate_with do |accept|
- accept.token_types(:personal_access_token, :deploy_token, :job_token)
+ accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username)
.sent_through(:http_basic_auth)
end
@@ -126,7 +126,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'Release.gpg' do
distribution_from!(project_or_group).file_signature
end
@@ -145,7 +144,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'Release' do
distribution = distribution_from!(project_or_group)
track_debian_package_event LIST_PACKAGE
@@ -166,7 +164,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'InRelease' do
distribution = distribution_from!(project_or_group)
track_debian_package_event LIST_PACKAGE
@@ -200,7 +197,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'Packages' do
present_index_file!(:di_packages)
end
@@ -222,7 +218,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'by-hash/SHA256/:file_sha256' do
present_index_file!(:di_packages)
end
@@ -246,7 +241,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'Sources' do
present_index_file!(:sources)
end
@@ -268,7 +262,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'by-hash/SHA256/:file_sha256' do
present_index_file!(:sources)
end
@@ -296,7 +289,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'Packages' do
present_index_file!(:packages)
end
@@ -318,7 +310,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'by-hash/SHA256/:file_sha256' do
present_index_file!(:packages)
end
diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb
index 67416e62f7d..7c64dc2f877 100644
--- a/lib/api/debian_group_packages.rb
+++ b/lib/api/debian_group_packages.rb
@@ -9,16 +9,16 @@ module API
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
helpers do
def project_or_group
- user_group
+ find_authorized_group!
end
end
after_validation do
require_packages_enabled!
- not_found! unless ::Feature.enabled?(:debian_group_packages, user_group)
+ not_found! unless ::Feature.enabled?(:debian_group_packages, project_or_group)
- authorize_read_package!(user_group)
+ authorize_read_package!(project_or_group)
end
params do
@@ -45,7 +45,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do
present_distribution_package_file!(find_project!(params[:project_id]))
end
diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb
index b7350efb49f..4f78ac926d8 100644
--- a/lib/api/debian_project_packages.rb
+++ b/lib/api/debian_project_packages.rb
@@ -17,7 +17,7 @@ module API
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
helpers do
def project_or_group
- user_project(action: :read_package)
+ authorized_user_project(action: :read_package)
end
end
@@ -52,7 +52,6 @@ module API
tags %w[debian_packages]
end
- route_setting :authentication, authenticate_non_public: true
get 'pool/:distribution/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do
present_distribution_package_file!(project_or_group)
end
@@ -85,10 +84,9 @@ module API
requires :file_name, type: String, desc: 'The filename', regexp: { value: Gitlab::Regex.debian_direct_upload_filename_regex, message: 'Only debs, udebs and ddebs can be directly added to a distribution' }
end
end
- route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true
put do
- authorize_upload!(authorized_user_project)
- bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:debian_max_file_size, params[:file].size)
+ authorize_upload!(project_or_group)
+ bad_request!('File is too large') if project_or_group.actual_limits.exceeded?(:debian_max_file_size, params[:file].size)
file_params = {
file: params['file'],
@@ -101,10 +99,10 @@ module API
package = if params[:distribution].present?
::Packages::CreateTemporaryPackageService.new(
- authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job)
+ project_or_group, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:debian, name: ::Packages::Debian::TEMPORARY_PACKAGE_NAME)
else
- ::Packages::Debian::FindOrCreateIncomingService.new(authorized_user_project, current_user).execute
+ ::Packages::Debian::FindOrCreateIncomingService.new(project_or_group, current_user).execute
end
::Packages::Debian::CreatePackageFileService.new(package: package, current_user: current_user, params: file_params).execute
@@ -113,7 +111,7 @@ module API
created!
rescue ObjectStorage::RemoteStoreError => e
- Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id })
+ Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
forbidden!
end
@@ -137,11 +135,10 @@ module API
requires :file_name, type: String, desc: 'The filename', regexp: { value: Gitlab::Regex.debian_direct_upload_filename_regex, message: 'Only debs, udebs and ddebs can be directly added to a distribution' }
end
end
- route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true
put 'authorize' do
authorize_workhorse!(
- subject: authorized_user_project,
- maximum_size: authorized_user_project.actual_limits.debian_max_file_size
+ subject: project_or_group,
+ maximum_size: project_or_group.actual_limits.debian_max_file_size
)
end
end
diff --git a/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb b/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb
index 0e38be3b4c9..0b79bc143db 100644
--- a/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb
+++ b/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings.rb
@@ -20,33 +20,28 @@ module Gitlab
end
def perform
- each_sub_batch do |sub_batch|
- migrate_remediations(sub_batch)
+ each_sub_batch(batching_scope: ->(relation) { relation.select(:id, :raw_metadata) }) do |findings|
+ migrate_remediations(
+ findings,
+ Link
+ .where(vulnerability_occurrence_id: findings.map(&:id))
+ .group(:vulnerability_occurrence_id, :name, :url)
+ .count
+ )
end
end
private
- def migrate_remediations(sub_batch)
- sub_batch.each do |finding|
- links = extract_links(finding.raw_metadata)
-
- list_of_attrs = links.map do |link|
- build_link(finding, link)
- end
-
- next unless list_of_attrs.present?
-
- begin
- create_links(list_of_attrs)
- rescue ActiveRecord::RecordNotUnique
- rescue StandardError => e
- logger.error(
- message: e.message,
- class: self.class.name,
- model_id: finding.id
- )
- end
+ def migrate_remediations(findings, existing_links)
+ findings.each do |finding|
+ create_links(build_links_from(finding, existing_links))
+ rescue ActiveRecord::StatementInvalid => e
+ logger.error(
+ message: e.message,
+ class: self.class.name,
+ model_id: finding.id
+ )
end
end
@@ -61,7 +56,16 @@ module Gitlab
}
end
+ def build_links_from(finding, existing_links)
+ extract_links(finding.raw_metadata).filter_map do |link|
+ key = [finding.id, link['name'], link['url']]
+ build_link(finding, link) unless existing_links.key?(key)
+ end
+ end
+
def create_links(attributes)
+ return if attributes.empty?
+
Link.upsert_all(attributes, returning: false)
end
@@ -72,6 +76,12 @@ module Gitlab
return [] if parsed_links.blank?
parsed_links.select { |link| link.try(:[], 'url').present? }.uniq
+ rescue JSON::ParserError => e
+ logger.warn(
+ message: e.message,
+ class: self.class.name
+ )
+ []
end
def logger
diff --git a/lib/gitlab/utils/error_message.rb b/lib/gitlab/utils/error_message.rb
index 72b69fb078f..cd9af95bfc3 100644
--- a/lib/gitlab/utils/error_message.rb
+++ b/lib/gitlab/utils/error_message.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def prefixed_error_message(message, prefix)
- "#{prefix}: #{message}"
+ "#{prefix} #{message}"
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f5f07e0eed0..6f958831f98 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18729,6 +18729,9 @@ msgstr ""
msgid "ForksDivergence|Update fork"
msgstr ""
+msgid "ForksDivergence|View merge request"
+msgstr ""
+
msgid "Format: %{dateFormat}"
msgstr ""
@@ -31270,7 +31273,7 @@ msgstr ""
msgid "PackageRegistry|Deleting the last package asset will remove version %{version} of %{name}. Are you sure?"
msgstr ""
-msgid "PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete the package anyway? %{docLinkStart}What are the risks?%{docLinkEnd}"
+msgid "PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete %{name} version %{version} anyway? %{docLinkStart}What are the risks?%{docLinkEnd}"
msgstr ""
msgid "PackageRegistry|Duplicate packages"
diff --git a/package.json b/package.json
index 1470a17c60b..ad3431fa9bc 100644
--- a/package.json
+++ b/package.json
@@ -59,12 +59,12 @@
"@gitlab/svgs": "3.38.0",
"@gitlab/ui": "60.1.0",
"@gitlab/visual-review-tools": "1.7.3",
- "@gitlab/web-ide": "0.0.1-dev-20230407181558",
+ "@gitlab/web-ide": "0.0.1-dev-20230418150125",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@popperjs/core": "^2.11.2",
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",
- "@sourcegraph/code-host-integration": "0.0.84",
+ "@sourcegraph/code-host-integration": "0.0.91",
"@tiptap/core": "^2.0.3",
"@tiptap/extension-blockquote": "^2.0.3",
"@tiptap/extension-bold": "^2.0.3",
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index e91e6673498..088b5b11a9a 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -510,6 +510,33 @@ RSpec.describe 'Group', feature_category: :subgroups do
end
end
end
+
+ context 'when in a private group' do
+ before do
+ group.update!(
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE,
+ project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS
+ )
+ end
+
+ context 'when visibility levels have been restricted to private only by an administrator' do
+ before do
+ stub_application_setting(
+ restricted_visibility_levels: [
+ Gitlab::VisibilityLevel::PRIVATE
+ ]
+ )
+ end
+
+ it 'does not display the "New project" button' do
+ visit group_path(group)
+
+ page.within '[data-testid="group-buttons"]' do
+ expect(page).not_to have_link('New project')
+ end
+ end
+ end
+ end
end
describe 'group README', :js do
diff --git a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
index b0fc9ef0f0c..4ab81182c9a 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
@@ -1,13 +1,13 @@
-import { GlModal as RealGlModal, GlSprintf } from '@gitlab/ui';
+import { GlModal as RealGlModal, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
import {
DELETE_PACKAGE_MODAL_PRIMARY_ACTION,
- DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT,
DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION,
DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT,
DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION,
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
} from '~/packages_and_registries/package_registry/constants';
const GlModal = stubComponent(RealGlModal, {
@@ -21,16 +21,17 @@ describe('DeleteModal', () => {
const defaultItemsToBeDeleted = [
{
- name: 'package 01',
+ name: 'package-1',
version: '1.0.0',
},
{
- name: 'package 02',
+ name: 'package-2',
version: '1.0.0',
},
];
const findModal = () => wrapper.findComponent(GlModal);
+ const findLink = () => wrapper.findComponent(GlLink);
const mountComponent = ({
itemsToBeDeleted = defaultItemsToBeDeleted,
@@ -73,7 +74,7 @@ describe('DeleteModal', () => {
mountComponent({ itemsToBeDeleted: [defaultItemsToBeDeleted[0]] });
expect(findModal().text()).toMatchInterpolatedText(
- 'You are about to delete version 1.0.0 of package 01. Are you sure?',
+ 'You are about to delete version 1.0.0 of package-1. Are you sure?',
);
});
@@ -92,6 +93,13 @@ describe('DeleteModal', () => {
);
});
+ it('contains link to help page', () => {
+ mountComponent({ showRequestForwardingContent: true });
+
+ expect(findLink().exists()).toBe(true);
+ expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH);
+ });
+
it('sets the right action primary text', () => {
mountComponent({ showRequestForwardingContent: true });
@@ -110,10 +118,15 @@ describe('DeleteModal', () => {
it('renders correct description', () => {
expect(findModal().text()).toMatchInterpolatedText(
- DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT,
+ 'Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete package-1 version 1.0.0 anyway? What are the risks?',
);
});
+ it('contains link to help page', () => {
+ expect(findLink().exists()).toBe(true);
+ expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH);
+ });
+
it('sets the right action primary text', () => {
expect(findModal().props('actionPrimary')).toMatchObject({
text: DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION,
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
index a700f42d367..e9f2a2c5095 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
@@ -7,7 +7,6 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
-import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
@@ -46,7 +45,6 @@ describe('PackageVersionsList', () => {
findListRow: () => wrapper.findComponent(VersionRow),
findAllListRow: () => wrapper.findAllComponents(VersionRow),
findDeletePackagesModal: () => wrapper.findComponent(DeleteModal),
- findPackageListDeleteModal: () => wrapper.findComponent(DeletePackageModal),
};
const mountComponent = ({
@@ -247,7 +245,7 @@ describe('PackageVersionsList', () => {
`('$description', ({ finderFunction, deletePayload }) => {
let eventSpy;
const category = 'UI::NpmPackages';
- const { findPackageListDeleteModal } = uiElements;
+ const { findDeletePackagesModal } = uiElements;
beforeEach(async () => {
eventSpy = jest.spyOn(Tracking, 'event');
@@ -256,10 +254,10 @@ describe('PackageVersionsList', () => {
finderFunction().vm.$emit('delete', deletePayload);
});
- it('passes itemToBeDeleted to the modal', () => {
- expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(
+ it('passes itemsToBeDeleted to the modal', () => {
+ expect(findDeletePackagesModal().props('itemsToBeDeleted')).toStrictEqual([
packageVersions()[0],
- );
+ ]);
});
it('requesting delete tracks the right action', () => {
@@ -272,7 +270,7 @@ describe('PackageVersionsList', () => {
describe('when modal confirms', () => {
beforeEach(() => {
- findPackageListDeleteModal().vm.$emit('ok');
+ findDeletePackagesModal().vm.$emit('confirm');
});
it('emits delete when modal confirms', () => {
@@ -288,14 +286,14 @@ describe('PackageVersionsList', () => {
});
});
- it.each(['ok', 'cancel'])('resets itemToBeDeleted when modal emits %s', async (event) => {
- await findPackageListDeleteModal().vm.$emit(event);
+ it.each(['confirm', 'cancel'])('resets itemsToBeDeleted when modal emits %s', async (event) => {
+ await findDeletePackagesModal().vm.$emit(event);
- expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull();
+ expect(findDeletePackagesModal().props('itemsToBeDeleted')).toEqual([]);
});
it('canceling delete tracks the right action', () => {
- findPackageListDeleteModal().vm.$emit('cancel');
+ findDeletePackagesModal().vm.$emit('cancel');
expect(eventSpy).toHaveBeenCalledWith(
category,
@@ -383,4 +381,15 @@ describe('PackageVersionsList', () => {
});
});
});
+
+ describe('with isRequestForwardingEnabled prop', () => {
+ const { findDeletePackagesModal } = uiElements;
+
+ it.each([true, false])('sets modal prop showRequestForwardingContent to %s', async (value) => {
+ mountComponent({ props: { isRequestForwardingEnabled: value } });
+ await waitForPromises();
+
+ expect(findDeletePackagesModal().props('showRequestForwardingContent')).toBe(value);
+ });
+ });
});
diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js
index 9054e4998bb..5fb53566d4e 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -16,7 +16,7 @@ export const packagePipelines = (extend) => [
ref: 'master',
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
project: {
- id: '1',
+ id: '14',
name: 'project14',
webUrl: 'http://gdk.test:3000/namespace14/project14',
__typename: 'Project',
@@ -219,7 +219,10 @@ export const pagination = (extend) => ({
...extend,
});
-export const packageDetailsQuery = (extendPackage) => ({
+export const packageDetailsQuery = ({
+ extendPackage = {},
+ packageSettings = defaultPackageGroupSettings,
+} = {}) => ({
data: {
package: {
...packageData(),
@@ -235,6 +238,12 @@ export const packageDetailsQuery = (extendPackage) => ({
path: 'projectPath',
name: 'gitlab-test',
fullPath: 'gitlab-test',
+ group: {
+ id: '1',
+ packageSettings,
+ __typename: 'Group',
+ },
+ __typename: 'Project',
},
tags: {
nodes: packageTags(),
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
index e1765917035..0962b4fa757 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
@@ -1,4 +1,4 @@
-import { GlEmptyState, GlModal, GlTabs, GlTab, GlSprintf } from '@gitlab/ui';
+import { GlEmptyState, GlModal, GlTabs, GlTab, GlSprintf, GlLink } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
@@ -18,6 +18,7 @@ import PackageTitle from '~/packages_and_registries/package_registry/components/
import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue';
import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue';
import {
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
PACKAGE_TYPE_COMPOSER,
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
@@ -43,6 +44,7 @@ import {
packageFiles,
packageDestroyFilesMutation,
packageDestroyFilesMutationError,
+ defaultPackageGroupSettings,
} from '../mock_data';
jest.mock('~/alert');
@@ -125,6 +127,7 @@ describe('PackagesApp', () => {
const findDependencyRows = () => wrapper.findAllComponents(DependencyRow);
const findDeletePackageModal = () => wrapper.findAllComponents(DeletePackages).at(1);
const findDeletePackages = () => wrapper.findComponent(DeletePackages);
+ const findLink = () => wrapper.findComponent(GlLink);
it('renders an empty state component', async () => {
createComponent({ resolver: jest.fn().mockResolvedValue(emptyPackageDetailsQuery) });
@@ -184,7 +187,9 @@ describe('PackagesApp', () => {
createComponent({
resolver: jest.fn().mockResolvedValue(
packageDetailsQuery({
- packageType,
+ extendPackage: {
+ packageType,
+ },
}),
),
});
@@ -239,16 +244,55 @@ describe('PackagesApp', () => {
});
});
- it('shows the delete confirmation modal when delete is clicked', async () => {
- createComponent();
+ describe('when delete button is clicked', () => {
+ describe('with request forwarding enabled', () => {
+ beforeEach(async () => {
+ const resolver = jest.fn().mockResolvedValue(
+ packageDetailsQuery({
+ packageSettings: {
+ ...defaultPackageGroupSettings,
+ npmPackageRequestsForwarding: true,
+ },
+ }),
+ );
+ createComponent({ resolver });
- await waitForPromises();
+ await waitForPromises();
- await findDeleteButton().trigger('click');
+ await findDeleteButton().trigger('click');
+ });
- expect(findDeleteModal().text()).toBe(
- 'You are about to delete version 1.0.0 of @gitlab-org/package-15. Are you sure?',
- );
+ it('shows the delete confirmation modal with request forwarding content', () => {
+ expect(findDeleteModal().text()).toBe(
+ 'Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete @gitlab-org/package-15 version 1.0.0 anyway? What are the risks?',
+ );
+ });
+
+ it('contains link to help page', () => {
+ expect(findLink().exists()).toBe(true);
+ expect(findLink().attributes('href')).toBe(REQUEST_FORWARDING_HELP_PAGE_PATH);
+ });
+ });
+
+ it('shows the delete confirmation modal without request forwarding content', async () => {
+ const resolver = jest.fn().mockResolvedValue(
+ packageDetailsQuery({
+ packageSettings: {
+ ...defaultPackageGroupSettings,
+ npmPackageRequestsForwarding: false,
+ },
+ }),
+ );
+ createComponent({ resolver });
+
+ await waitForPromises();
+
+ await findDeleteButton().trigger('click');
+
+ expect(findDeleteModal().text()).toBe(
+ 'You are about to delete version 1.0.0 of @gitlab-org/package-15. Are you sure?',
+ );
+ });
});
describe('successful request', () => {
@@ -302,7 +346,9 @@ describe('PackagesApp', () => {
createComponent({
resolver: jest
.fn()
- .mockResolvedValue(packageDetailsQuery({ packageType: PACKAGE_TYPE_COMPOSER })),
+ .mockResolvedValue(
+ packageDetailsQuery({ extendPackage: { packageType: PACKAGE_TYPE_COMPOSER } }),
+ ),
});
await waitForPromises();
@@ -341,12 +387,18 @@ describe('PackagesApp', () => {
const [packageFile] = packageFiles();
const resolver = jest.fn().mockResolvedValue(
packageDetailsQuery({
- packageFiles: {
- pageInfo: {
- hasNextPage: false,
+ extendPackage: {
+ packageFiles: {
+ pageInfo: {
+ hasNextPage: false,
+ },
+ nodes: [packageFile],
+ __typename: 'PackageFileConnection',
},
- nodes: [packageFile],
- __typename: 'PackageFileConnection',
+ },
+ packageSettings: {
+ ...defaultPackageGroupSettings,
+ npmPackageRequestsForwarding: false,
},
}),
);
@@ -523,11 +575,17 @@ describe('PackagesApp', () => {
it('opens the delete package confirmation modal', async () => {
const resolver = jest.fn().mockResolvedValue(
packageDetailsQuery({
- packageFiles: {
- pageInfo: {
- hasNextPage: false,
+ extendPackage: {
+ packageFiles: {
+ pageInfo: {
+ hasNextPage: false,
+ },
+ nodes: packageFiles(),
},
- nodes: packageFiles(),
+ },
+ packageSettings: {
+ ...defaultPackageGroupSettings,
+ npmPackageRequestsForwarding: false,
},
}),
);
@@ -565,8 +623,10 @@ describe('PackagesApp', () => {
createComponent({
resolver: jest.fn().mockResolvedValue(
packageDetailsQuery({
- versions: {
- count: 0,
+ extendPackage: {
+ versions: {
+ count: 0,
+ },
},
}),
),
@@ -591,6 +651,7 @@ describe('PackagesApp', () => {
count: packageVersions().length,
isMutationLoading: false,
packageId: 'gid://gitlab/Packages::Package/1',
+ isRequestForwardingEnabled: true,
});
});
@@ -646,8 +707,10 @@ describe('PackagesApp', () => {
createComponent({
resolver: jest.fn().mockResolvedValue(
packageDetailsQuery({
- packageType: PACKAGE_TYPE_NUGET,
- dependencyLinks: { nodes: [] },
+ extendPackage: {
+ packageType: PACKAGE_TYPE_NUGET,
+ dependencyLinks: { nodes: [] },
+ },
}),
),
});
@@ -663,7 +726,9 @@ describe('PackagesApp', () => {
createComponent({
resolver: jest.fn().mockResolvedValue(
packageDetailsQuery({
- packageType: PACKAGE_TYPE_NUGET,
+ extendPackage: {
+ packageType: PACKAGE_TYPE_NUGET,
+ },
}),
),
});
diff --git a/spec/frontend/repository/components/fork_info_spec.js b/spec/frontend/repository/components/fork_info_spec.js
index 8521f91a6c7..62a66e59d24 100644
--- a/spec/frontend/repository/components/fork_info_spec.js
+++ b/spec/frontend/repository/components/fork_info_spec.js
@@ -86,6 +86,7 @@ describe('ForkInfo component', () => {
const findIcon = () => wrapper.findComponent(GlIcon);
const findUpdateForkButton = () => wrapper.findByTestId('update-fork-button');
const findCreateMrButton = () => wrapper.findByTestId('create-mr-button');
+ const findViewMrButton = () => wrapper.findByTestId('view-mr-button');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findDivergenceMessage = () => wrapper.findByTestId('divergence-message');
const findInaccessibleMessage = () => wrapper.findByTestId('inaccessible-project');
@@ -145,8 +146,14 @@ describe('ForkInfo component', () => {
expect(findCreateMrButton().attributes('href')).toBe(propsForkInfo.createMrPath);
});
- it('does not render create MR button if user had no permission to Create MR in fork', async () => {
- await createComponent({ canUserCreateMrInFork: false });
+ it('renders View MR Button with correct path', async () => {
+ const viewMrPath = 'path/to/view/mr';
+ await createComponent({ viewMrPath });
+ expect(findViewMrButton().attributes('href')).toBe(viewMrPath);
+ });
+
+ it('does not render create MR button if create MR path is blank', async () => {
+ await createComponent({ createMrPath: '' });
expect(findCreateMrButton().exists()).toBe(false);
});
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index afa183c0616..a8768e2a825 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -127,7 +127,6 @@ export const propsForkInfo = {
aheadComparePath: '/nataliia/myGitLab/-/compare/main...ref?from_project_id=1',
behindComparePath: 'gitlab-org/gitlab/-/compare/ref...main?from_project_id=2',
createMrPath: 'path/to/new/mr',
- canUserCreateMrInFork: true,
};
export const propsConflictsModal = {
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 80cb0ea67da..2e42cd2daad 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -291,7 +291,7 @@ RSpec.describe GitlabSchema.types['Project'] do
let_it_be(:project) { create(:project_empty_repo) }
it 'raises an error' do
- expect(subject['errors'][0]['message']).to eq('UF: You must add at least one file to the ' \
'repository before using Security features.')
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 7cf2de820fc..c4ab9fb115f 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1359,11 +1359,16 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
end
context 'when fork source is available' do
- it 'returns the data related to fork divergence' do
- source_project = project_with_repo
+ let_it_be(:fork_network) { create(:fork_network, root_project: project_with_repo) }
+ let_it_be(:source_project) { project_with_repo }
- allow(helper).to receive(:visible_fork_source).with(project).and_return(source_project)
- allow(helper).to receive(:can_user_create_mr_in_fork).with(source_project).and_return(false)
+ before_all do
+ project.fork_network = fork_network
+ project.add_developer(user)
+ source_project.add_developer(user)
+ end
+
+ it 'returns the data related to fork divergence' do
allow(helper).to receive(:current_user).and_return(user)
ahead_path =
@@ -1382,9 +1387,44 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
behind_compare_path: behind_path,
source_default_branch: source_project.default_branch,
create_mr_path: create_mr_path,
- can_user_create_mr_in_fork: false
+ view_mr_path: nil
})
end
+
+ it 'returns view_mr_path if a merge request for the branch exists' do
+ allow(helper).to receive(:current_user).and_return(user)
+
+ merge_request =
+ create(:merge_request, source_project: project, target_project: project_with_repo,
+ source_branch: 'ref', target_branch: project_with_repo.default_branch)
+
+ expect(helper.vue_fork_divergence_data(project, 'ref')).to include({
+ create_mr_path: nil,
+ view_mr_path: "/#{source_project.full_path}/-/merge_requests/#{merge_request.iid}"
+ })
+ end
+
+ context 'when a user cannot create a merge request' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_role, :source_project_role) do
+ :guest | :developer
+ :developer | :guest
+ end
+
+ with_them do
+ it 'create_mr_path is nil' do
+ allow(helper).to receive(:current_user).and_return(user)
+
+ project.add_member(user, project_role)
+ source_project.add_member(user, source_project_role)
+
+ expect(helper.vue_fork_divergence_data(project, 'ref')).to include({
+ create_mr_path: nil, view_mr_path: nil
+ })
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb b/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb
index 9a90af968e2..b973f9d4350 100644
--- a/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_links_for_vulnerability_findings_spec.rb
@@ -139,33 +139,18 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateLinksForVulnerabilityFindings
expect { perform_migration }.to change { vulnerability_finding_links.count }.by(2)
end
+ end
- context 'when create throws exception ActiveRecord::RecordNotUnique' do
- before do
- allow(migration).to receive(:create_links).and_raise(ActiveRecord::RecordNotUnique)
- end
-
- it 'does not log this error nor create new records' do
- expect(Gitlab::AppLogger).not_to receive(:error)
-
- expect { perform_migration }.not_to change { vulnerability_finding_links.count }
- end
+ context 'when Gitlab::Json throws exception JSON::ParserError' do
+ before do
+ create_finding!(project1.id, scanner1.id, { links: [link_hash] })
+ allow(Gitlab::Json).to receive(:parse).and_raise(JSON::ParserError)
end
- context 'when create throws exception StandardError' do
- before do
- allow(migration).to receive(:create_links).and_raise(StandardError)
- end
+ it 'does not log this error nor create new records' do
+ expect(Gitlab::AppLogger).not_to receive(:error)
- it 'logs StandardError' do
- expect(Gitlab::AppLogger).to receive(:error).with({
- class: described_class.name, message: StandardError.to_s, model_id: finding1.id
- })
- expect(Gitlab::AppLogger).to receive(:error).with({
- class: described_class.name, message: StandardError.to_s, model_id: finding2.id
- })
- expect { perform_migration }.not_to change { vulnerability_finding_links.count }
- end
+ expect { perform_migration }.not_to change { vulnerability_finding_links.count }
end
end
@@ -181,6 +166,10 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateLinksForVulnerabilityFindings
expect { perform_migration }.not_to change { vulnerability_finding_links.count }
end
+
+ it 'does not raise ActiveRecord::RecordNotUnique' do
+ expect { perform_migration }.not_to raise_error
+ end
end
private
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index 3a885d70eb4..1fb442a74fb 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -73,9 +73,8 @@ RSpec.describe Gitlab::BareRepositoryImport::Importer do
end
it 'does not schedule an import' do
- expect_next_instance_of(Project) do |instance|
- expect(instance).not_to receive(:import_schedule)
- end
+ project = Project.find_by_full_path(project_path)
+ expect(project).not_to receive(:import_schedule)
importer.create_project_if_needed
end
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index 236e04a041b..7ecdc5d25ea 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -27,8 +27,8 @@ RSpec.describe Gitlab::BitbucketImport::ProjectCreator do
end
it 'creates project' do
- expect_next_instance_of(Project) do |project|
- expect(project).to receive(:add_import_job)
+ allow_next_instance_of(Project) do |project|
+ allow(project).to receive(:add_import_job)
end
project_creator = described_class.new(repo, 'vim', namespace, user, access_params)
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
index 53bf1db3438..59a98987f7d 100644
--- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -24,8 +24,8 @@ RSpec.describe Gitlab::GitlabImport::ProjectCreator do
end
it 'creates project' do
- expect_next_instance_of(Project) do |project|
- expect(project).to receive(:add_import_job)
+ allow_next_instance_of(Project) do |project|
+ allow(project).to receive(:add_import_job)
end
project_creator = described_class.new(repo, namespace, user, access_params)
diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
index 17ecd183ac9..f1dbe2bf8e2 100644
--- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::LegacyGithubImport::ProjectCreator do
before do
namespace.add_owner(user)
- expect_next_instance_of(Project) do |project|
+ allow_next_instance_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
end
diff --git a/spec/lib/gitlab/utils/error_message_spec.rb b/spec/lib/gitlab/utils/error_message_spec.rb
index 17786f2c8ef..a6de2520c5e 100644
--- a/spec/lib/gitlab/utils/error_message_spec.rb
+++ b/spec/lib/gitlab/utils/error_message_spec.rb
@@ -15,14 +15,14 @@ RSpec.describe Gitlab::Utils::ErrorMessage, feature_category: :error_tracking do
describe '#to_user_facing' do
it 'returns a user-facing error message with the UF prefix' do
- expect(described_class.to_user_facing(message)).to eq("UF: #{message}")
+ expect(described_class.to_user_facing(message)).to eq("UF #{message}")
end
end
describe '#prefixed_error_message' do
it 'returns a message with the given prefix' do
prefix = 'ERROR'
- expect(described_class.prefixed_error_message(message, prefix)).to eq("#{prefix}: #{message}")
+ expect(described_class.prefixed_error_message(message, prefix)).to eq("#{prefix} #{message}")
end
end
end
diff --git a/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb b/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb
index 45c00416bcc..b28b1ea0730 100644
--- a/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb
+++ b/spec/migrations/20230314144640_reschedule_migration_for_links_spec.rb
@@ -10,13 +10,7 @@ RSpec.describe RescheduleMigrationForLinks, :migration, feature_category: :vulne
it 'schedules a batched background migration' do
migrate!
- expect(migration).to have_scheduled_batched_migration(
- table_name: :vulnerability_occurrences,
- column_name: :id,
- interval: described_class::DELAY_INTERVAL,
- batch_size: described_class::BATCH_SIZE,
- sub_batch_size: described_class::SUB_BATCH_SIZE
- )
+ expect(migration).not_to have_scheduled_batched_migration
end
end
diff --git a/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb b/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb
new file mode 100644
index 00000000000..06eccf03ca4
--- /dev/null
+++ b/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RescheduleLinksAvoidingDuplication, :migration, feature_category: :vulnerability_management do
+ let(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ it 'schedules a batched background migration' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :vulnerability_occurrences,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migrate!
+ schema_migrate_down!
+
+ expect(migration).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 935b9124534..462848bc832 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe GroupPolicy, feature_category: :system_access do
include AdminModeHelper
include_context 'GroupPolicy context'
+ using RSpec::Parameterized::TableSyntax
context 'public group with no user' do
let(:group) { create(:group, :public, :crm_enabled) }
@@ -668,6 +669,59 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
it { is_expected.to be_allowed(:create_projects) }
end
end
+
+ context 'with visibility levels restricted by the administrator' do
+ let_it_be(:public) { Gitlab::VisibilityLevel::PUBLIC }
+ let_it_be(:internal) { Gitlab::VisibilityLevel::INTERNAL }
+ let_it_be(:private) { Gitlab::VisibilityLevel::PRIVATE }
+ let_it_be(:policy) { :create_projects }
+
+ where(:restricted_visibility_levels, :group_visibility, :can_create_project?) do
+ [] | ref(:public) | true
+ [] | ref(:internal) | true
+ [] | ref(:private) | true
+ [ref(:public)] | ref(:public) | true
+ [ref(:public)] | ref(:internal) | true
+ [ref(:public)] | ref(:private) | true
+ [ref(:internal)] | ref(:public) | true
+ [ref(:internal)] | ref(:internal) | true
+ [ref(:internal)] | ref(:private) | true
+ [ref(:private)] | ref(:public) | true
+ [ref(:private)] | ref(:internal) | true
+ [ref(:private)] | ref(:private) | false
+ [ref(:public), ref(:internal)] | ref(:public) | true
+ [ref(:public), ref(:internal)] | ref(:internal) | true
+ [ref(:public), ref(:internal)] | ref(:private) | true
+ [ref(:public), ref(:private)] | ref(:public) | true
+ [ref(:public), ref(:private)] | ref(:internal) | true
+ [ref(:public), ref(:private)] | ref(:private) | false
+ [ref(:private), ref(:internal)] | ref(:public) | true
+ [ref(:private), ref(:internal)] | ref(:internal) | false
+ [ref(:private), ref(:internal)] | ref(:private) | false
+ [ref(:public), ref(:internal), ref(:private)] | ref(:public) | false
+ [ref(:public), ref(:internal), ref(:private)] | ref(:internal) | false
+ [ref(:public), ref(:internal), ref(:private)] | ref(:private) | false
+ end
+
+ with_them do
+ before do
+ group.update!(visibility_level: group_visibility)
+ stub_application_setting(restricted_visibility_levels: restricted_visibility_levels)
+ end
+
+ context 'with non-admin user' do
+ let(:current_user) { owner }
+
+ it { is_expected.to(can_create_project? ? be_allowed(policy) : be_disallowed(policy)) }
+ end
+
+ context 'with admin user', :enable_admin_mode do
+ let(:current_user) { admin }
+
+ it { is_expected.to be_allowed(policy) }
+ end
+ end
+ end
end
context 'import_projects' do
@@ -878,7 +932,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
end
end
- %w(guest reporter developer maintainer owner).each do |role|
+ %w[guest reporter developer maintainer owner].each do |role|
context role do
let(:current_user) { send(role) }
@@ -1046,8 +1100,6 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
end
describe 'observability' do
- using RSpec::Parameterized::TableSyntax
-
let(:allowed_admin) { be_allowed(:read_observability) && be_allowed(:admin_observability) }
let(:allowed_read) { be_allowed(:read_observability) && be_disallowed(:admin_observability) }
let(:disallowed) { be_disallowed(:read_observability) && be_disallowed(:admin_observability) }
@@ -1661,8 +1713,6 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
describe 'read_usage_quotas policy' do
context 'reading usage quotas' do
- using RSpec::Parameterized::TableSyntax
-
let(:policy) { :read_usage_quotas }
where(:role, :admin_mode, :allowed) do
diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb
index 18e5bfd711e..9c726e5a5f7 100644
--- a/spec/requests/api/debian_group_packages_spec.rb
+++ b/spec/requests/api/debian_group_packages_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do
shared_examples 'not a Debian package tracking event' do
include_context 'Debian repository access', :public, :developer, :basic do
- it_behaves_like 'not a package tracking event'
+ it_behaves_like 'not a package tracking event', described_class.name, /.*/
end
end
diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb
index 17a413ed059..030962044c6 100644
--- a/spec/requests/api/debian_project_packages_spec.rb
+++ b/spec/requests/api/debian_project_packages_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
shared_examples 'not a Debian package tracking event' do
include_context 'Debian repository access', :public, :developer, :basic do
- it_behaves_like 'not a package tracking event'
+ it_behaves_like 'not a package tracking event', described_class.name, /.*/
end
end
@@ -180,6 +180,7 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d
let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' }
it_behaves_like 'Debian packages write endpoint', 'upload', :created, nil
+ it_behaves_like 'Debian packages endpoint catching ObjectStorage::RemoteStoreError'
it_behaves_like 'a Debian package tracking event', 'push_package'
context 'with codename and component' do
diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb
index e51ea0e2ad6..b2d087819a1 100644
--- a/spec/support/shared_examples/features/packages_shared_examples.rb
+++ b/spec/support/shared_examples/features/packages_shared_examples.rb
@@ -19,6 +19,10 @@ RSpec.shared_examples 'packages list' do |check_project_name: false|
end
RSpec.shared_examples 'package details link' do |property|
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ end
+
it 'navigates to the correct url' do
page.within(packages_table_selector) do
click_link package.name
diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
index 66554f18e80..bc7ad570441 100644
--- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb
@@ -166,6 +166,18 @@ RSpec.shared_examples 'Debian packages write endpoint' do |desired_behavior, suc
it_behaves_like 'rejects Debian access with unknown container id', :unauthorized, :basic
end
+RSpec.shared_examples 'Debian packages endpoint catching ObjectStorage::RemoteStoreError' do
+ include_context 'Debian repository access', :public, :developer, :basic do
+ it "returns forbidden" do
+ expect(::Packages::Debian::CreatePackageFileService).to receive(:new).and_raise ObjectStorage::RemoteStoreError
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+end
+
RSpec.shared_examples 'Debian packages index endpoint' do |success_body|
it_behaves_like 'Debian packages read endpoint', 'GET', :success, success_body
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index 1f2450c864b..678c73637ee 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -161,11 +161,11 @@ RSpec.shared_examples 'a package tracking event' do |category, action, service_p
end
end
-RSpec.shared_examples 'not a package tracking event' do
+RSpec.shared_examples 'not a package tracking event' do |category, action|
it 'does not create a gitlab tracking event', :snowplow, :aggregate_failures do
subject
- expect_no_snowplow_event
+ expect_no_snowplow_event category: category, action: action
end
end
diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
index 094c91f2ab5..21dc3c2bf70 100644
--- a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
@@ -169,7 +169,7 @@ RSpec.shared_examples_for 'services security ci configuration create service' do
it 'returns an error' do
expect { result }.to raise_error { |error|
expect(error).to be_a(Gitlab::Graphql::Errors::MutationError)
- expect(error.message).to eq('UF: You must add at least one file to the repository' \
' before using Security features.')
diff --git a/yarn.lock b/yarn.lock
index b55329a7f1b..527cd89721e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1134,10 +1134,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.3.tgz#9ea641146436da388ffbad25d7f2abe0df52c235"
integrity sha512-NMV++7Ew1FSBDN1xiZaauU9tfeSfgDHcOLpn+8bGpP+O5orUPm2Eu66R5eC5gkjBPaXosNAxNWtriee+aFk4+g==
-"@gitlab/web-ide@0.0.1-dev-20230407181558":
- version "0.0.1-dev-20230407181558"
- resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20230407181558.tgz#69c5243e8c567ea3ef4879ede68566f99d47705a"
- integrity sha512-8YdZxGvXOX3hU/2F1XSwMWFRz56a/QLvY+amne7ie1NRzIVx6SmjKOxot+F1FhSXjc0zbwooGoOn8j6wkgdHCg==
+"@gitlab/web-ide@0.0.1-dev-20230418150125":
+ version "0.0.1-dev-20230418150125"
+ resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20230418150125.tgz#ea1cb4952c41e19f76465379c96967e98a1217ab"
+ integrity sha512-rdh8gsvGY6WAl0pGFGRVJIlKSiVY37NimKdOndUcUc62xiZqdT1+4aBmnIk9XjAromURsyFRD6Z9ABNhYx7SBA==
"@graphql-eslint/eslint-plugin@3.18.0":
version "3.18.0"
@@ -1797,10 +1797,10 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
-"@sourcegraph/code-host-integration@0.0.84":
- version "0.0.84"
- resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.84.tgz#680f5eadde4d05c1dfccb32e14b0a7e58365ebad"
- integrity sha512-xHaplY49vBHeXggcc0K3LNQ0VebKy3BwCSokXGxqHDaRJ8JmUwp/DyZX1RFp0Rx/UR6OPXQXKyfVqFcz/UlkUw==
+"@sourcegraph/code-host-integration@0.0.91":
+ version "0.0.91"
+ resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.91.tgz#2d284ea2ea1023fb3cdaac9aa61a86322d6801ff"
+ integrity sha512-sC/q64XUDZWoxyAkzpxaw5u2BFNDiPp9K4PybTJFRW1e3VQwul2/iXcUvMf1RzQv4AkQWO8uLeHHG0wRGfCPcA==
"@testing-library/dom@^7.16.2":
version "7.24.5"