Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-20 09:18:59 +00:00
parent 9b863f753f
commit c7eec01f1b
57 changed files with 689 additions and 299 deletions

View File

@ -1 +1 @@
dbf0cf7484ba1265ee1cd5e3a49e04da933e931c
d15b9c84faee3eb178e7c7d9360832f26d4107a2

View File

@ -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')"
>
<p v-if="showRequestForwardingContent">
<gl-sprintf :message="requestForwardingContentMessage">
<template #docLink="{ content }">
<p>
<gl-sprintf v-if="itemToBeDeleted" :message="packageDescription">
<template v-if="showRequestForwardingContent" #docLink="{ content }">
<gl-link :href="$options.links.REQUEST_FORWARDING_HELP_PAGE_PATH">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<p v-else>
<gl-sprintf v-if="itemToBeDeleted" :message="$options.i18n.DELETE_MODAL_CONTENT">
<template #version>
<strong>{{ itemToBeDeleted.version }}</strong>
</template>
<template #name>
<strong>{{ itemToBeDeleted.name }}</strong>
</template>
</gl-sprintf>
<gl-sprintf v-else :message="$options.i18n.DELETE_PACKAGES_MODAL_DESCRIPTION">
<gl-sprintf v-else :message="packagesDescription">
<template v-if="showRequestForwardingContent" #docLink="{ content }">
<gl-link :href="$options.links.REQUEST_FORWARDING_HELP_PAGE_PATH">{{ content }}</gl-link>
</template>
<template #count>
{{ itemsToBeDeleted.length }}
</template>

View File

@ -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)"
/>
</template>
</registry-list>
<delete-package-modal
:item-to-be-deleted="itemToBeDeleted"
@ok="deleteItemConfirmation"
@cancel="deleteItemCanceled"
/>
<delete-modal
ref="deletePackagesModal"
:items-to-be-deleted="itemsToBeDeleted"
:show-request-forwarding-content="isRequestForwardingEnabled"
@confirm="deleteItemsConfirmation"
@cancel="deleteItemsCanceled"
/>

View File

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

View File

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

View File

@ -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)"
>
<template #modal-title>{{ $options.i18n.DELETE_MODAL_TITLE }}</template>
<gl-sprintf :message="deletePackageModalContent">
<template #version>
<strong>{{ packageEntity.version }}</strong>
</template>
<p>
<gl-sprintf :message="deleteModalContent">
<template v-if="isRequestForwardingEnabled" #docLink="{ content }">
<gl-link :href="$options.links.REQUEST_FORWARDING_HELP_PAGE_PATH">{{
content
}}</gl-link>
</template>
<template #name>
<strong>{{ packageEntity.name }}</strong>
</template>
</gl-sprintf>
<template #version>
<strong>{{ packageEntity.version }}</strong>
</template>
<template #name>
<strong>{{ packageEntity.name }}</strong>
</template>
</gl-sprintf>
</p>
</gl-modal>
</template>
</delete-packages>

View File

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

View File

@ -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 {
>
<span>{{ $options.i18n.createMergeRequest }}</span>
</gl-button>
<gl-button
v-if="hasViewMrButton"
class="gl-ml-4"
:href="viewMrPath"
data-testid="view-mr-button"
>
<span>{{ $options.i18n.viewMergeRequest }}</span>
</gl-button>
<gl-button
v-if="hasUpdateButton"
class="gl-ml-4"

View File

@ -84,10 +84,10 @@ export default function setupVueRepositoryList() {
sourcePath,
sourceDefaultBranch,
createMrPath,
viewMrPath,
canSyncBranch,
aheadComparePath,
behindComparePath,
canUserCreateMrInFork,
} = forkEl.dataset;
return new Vue({
el: forkEl,
@ -104,7 +104,7 @@ export default function setupVueRepositoryList() {
aheadComparePath,
behindComparePath,
createMrPath,
canUserCreateMrInFork,
viewMrPath,
},
});
},

View File

@ -3,6 +3,7 @@
module ProjectsHelper
include Gitlab::Utils::StrongMemoize
include CompareHelper
include Gitlab::Allowable
def project_incident_management_setting
@project_incident_management_setting ||= @project.incident_management_setting ||
@ -131,6 +132,10 @@ module ProjectsHelper
source_default_branch = source_project.default_branch
merge_request =
MergeRequest.opened
.from_project(project).of_projects(source_project.id).from_source_branches(ref).first
{
project_path: project.full_path,
selected_branch: ref,
@ -141,11 +146,11 @@ module ProjectsHelper
ahead_compare_path: project_compare_path(
project, from: source_default_branch, to: ref, from_project_id: source_project.id
),
create_mr_path: create_mr_path(from: ref, source_project: project, to: source_default_branch, target_project: source_project),
create_mr_path: create_merge_request_path(project, source_project, ref, merge_request),
view_mr_path: merge_request && project_merge_request_path(source_project, merge_request),
behind_compare_path: project_compare_path(
source_project, from: ref, to: source_default_branch, from_project_id: project.id
),
can_user_create_mr_in_fork: can_user_create_mr_in_fork(source_project)
)
}
end
@ -167,10 +172,6 @@ module ProjectsHelper
project.fork_source if project.fork_source && can?(current_user, :read_project, project.fork_source)
end
def can_user_create_mr_in_fork(project)
can?(current_user, :create_merge_request_in, project)
end
def project_search_tabs?(tab)
return false unless @project.present?
@ -523,6 +524,18 @@ module ProjectsHelper
private
def create_merge_request_path(project, source_project, ref, merge_request)
return if merge_request.present?
return unless can?(current_user, :create_merge_request_from, project)
return unless can?(current_user, :create_merge_request_in, source_project)
create_mr_path(
from: ref,
source_project: project,
to: source_project.default_branch,
target_project: source_project)
end
def localized_access_names
{
Gitlab::Access::NO_ACCESS => _('No access'),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
777065413e8eb5605037885fb1a38c74b1f464733afb2718380f081edb9ab8a8

View File

@ -0,0 +1 @@
101dac198ed6204b5b74f809765d2a7f1907907fdfcfbe579989b8fcf61610c0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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`.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ module Gitlab
end
def prefixed_error_message(message, prefix)
"#{prefix}: #{message}"
"#{prefix} #{message}"
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <a target="_blank" rel="noopener noreferrer" ' \
expect(subject['errors'][0]['message']).to eq('UF You must <a target="_blank" rel="noopener noreferrer" ' \
'href="http://localhost/help/user/project/repository/index.md#' \
'add-files-to-a-repository">add at least one file to the ' \
'repository</a> before using Security features.')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <a target="_blank" rel="noopener noreferrer" ' \
expect(error.message).to eq('UF You must <a target="_blank" rel="noopener noreferrer" ' \
'href="http://localhost/help/user/project/repository/index.md' \
'#add-files-to-a-repository">add at least one file to the repository' \
'</a> before using Security features.')

View File

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