Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
265a7cecca
commit
f44215bf40
12
.rubocop.yml
12
.rubocop.yml
|
@ -596,6 +596,18 @@ RSpec/HaveGitlabHttpStatus:
|
|||
- 'spec/**/*'
|
||||
- 'ee/spec/**/*'
|
||||
|
||||
RSpec/ContextWording:
|
||||
Prefixes:
|
||||
- when
|
||||
- with
|
||||
- without
|
||||
- for
|
||||
- and
|
||||
- on
|
||||
- in
|
||||
- as
|
||||
- if
|
||||
|
||||
Style/MultilineWhenThen:
|
||||
Enabled: false
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -94,7 +94,6 @@ export const LFS_STORAGE = 'lfs';
|
|||
*/
|
||||
export const LEGACY_FILE_TYPES = [
|
||||
'gemfile',
|
||||
'gemspec',
|
||||
'composer_json',
|
||||
'podfile',
|
||||
'podspec',
|
||||
|
|
|
@ -147,3 +147,4 @@ export const HLJS_COMMENT_SELECTOR = 'hljs-comment';
|
|||
export const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight';
|
||||
|
||||
export const NPM_URL = 'https://npmjs.com/package';
|
||||
export const GEM_URL = 'https://rubygems.org/gems';
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import packageJsonLinker from './utils/package_json_linker';
|
||||
import gemspecLinker from './utils/gemspec_linker';
|
||||
|
||||
const DEPENDENCY_LINKERS = {
|
||||
package_json: packageJsonLinker,
|
||||
gemspec: gemspecLinker,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,4 +12,5 @@ export const createLink = (href, innerText) => {
|
|||
return link.outerHTML;
|
||||
};
|
||||
|
||||
export const generateHLJSOpenTag = (type) => `<span class="hljs-${escape(type)}">"`;
|
||||
export const generateHLJSOpenTag = (type, delimiter = '"') =>
|
||||
`<span class="hljs-${escape(type)}">${delimiter}`;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { GEM_URL } from '../../constants';
|
||||
import { createLink, generateHLJSOpenTag } from './dependency_linker_util';
|
||||
|
||||
const methodRegex = '.*add_dependency.*|.*add_runtime_dependency.*|.*add_development_dependency.*';
|
||||
const openTagRegex = generateHLJSOpenTag('string', '(&.*;)');
|
||||
const closeTagRegex = '&.*</span>';
|
||||
|
||||
const DEPENDENCY_REGEX = new RegExp(
|
||||
/*
|
||||
* Detects gemspec dependencies inside of content that is highlighted by Highlight.js
|
||||
* Example: s.add_dependency(<span class="hljs-string">'rugged'</span>, <span class="hljs-string">'~> 0.24.0'</span>)
|
||||
*
|
||||
* Group 1 (method) : s.add_dependency(
|
||||
* Group 2 (delimiter) : '
|
||||
* Group 3 (packageName): rugged
|
||||
* Group 4 (closeTag) : '</span>
|
||||
* Group 5 (rest) : , <span class="hljs-string">'~> 0.24.0'</span>)
|
||||
*/
|
||||
`(${methodRegex})${openTagRegex}(.*)(${closeTagRegex})(.*${closeTagRegex})`,
|
||||
'gm',
|
||||
);
|
||||
|
||||
const handleReplace = (method, delimiter, packageName, closeTag, rest) => {
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
const openTag = generateHLJSOpenTag('string linked', delimiter);
|
||||
const href = joinPaths(GEM_URL, packageName);
|
||||
const packageLink = createLink(href, packageName);
|
||||
|
||||
return `${method}${openTag}${packageLink}${closeTag}${rest}`;
|
||||
};
|
||||
|
||||
export default (result) => {
|
||||
return result.value.replace(
|
||||
DEPENDENCY_REGEX,
|
||||
(_, method, delimiter, packageName, closeTag, rest) =>
|
||||
handleReplace(method, delimiter, packageName, closeTag, rest),
|
||||
);
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
|
||||
|
||||
mutation createWorkItem($input: WorkItemCreateInput!) {
|
||||
workItemCreate(input: $input) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
|
||||
|
||||
mutation workItemCreateFromTask($input: WorkItemCreateFromTaskInput!) {
|
||||
workItemCreateFromTask(input: $input) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
|
||||
|
||||
mutation localUpdateWorkItem($input: LocalUpdateWorkItemInput) {
|
||||
localUpdateWorkItem(input: $input) @client {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
|
||||
|
||||
mutation workItemUpdate($input: WorkItemUpdateInput!) {
|
||||
workItemUpdate(input: $input) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
|
||||
|
||||
mutation workItemUpdateTask($input: WorkItemUpdateTaskInput!) {
|
||||
workItemUpdate: workItemUpdateTask(input: $input) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
|
||||
|
||||
mutation workItemUpdateWidgets($input: WorkItemUpdateWidgetsInput!) {
|
||||
workItemUpdateWidgets(input: $input) {
|
||||
|
|
|
@ -28,10 +28,6 @@ fragment WorkItem on WorkItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
... on WorkItemWidgetWeight {
|
||||
type
|
||||
weight
|
||||
}
|
||||
... on WorkItemWidgetHierarchy {
|
||||
type
|
||||
parent {
|
||||
|
@ -40,10 +36,8 @@ fragment WorkItem on WorkItem {
|
|||
title
|
||||
}
|
||||
children {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
}
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#import "~/graphql_shared/fragments/label.fragment.graphql"
|
||||
#import "./work_item.fragment.graphql"
|
||||
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
|
||||
|
||||
query workItem($id: WorkItemID!) {
|
||||
workItem(id: $id) {
|
||||
|
|
|
@ -47,7 +47,14 @@ class Import::BulkImportsController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
responses = create_params.map { |entry| ::BulkImports::CreateService.new(current_user, entry, credentials).execute }
|
||||
responses = create_params.map do |entry|
|
||||
if entry[:destination_name]
|
||||
entry[:destination_slug] ||= entry[:destination_name]
|
||||
entry.delete(:destination_name)
|
||||
end
|
||||
|
||||
::BulkImports::CreateService.new(current_user, entry, credentials).execute
|
||||
end
|
||||
|
||||
render json: responses.map { |response| { success: response.success?, id: response.payload[:id], message: response.message } }
|
||||
end
|
||||
|
@ -100,6 +107,7 @@ class Import::BulkImportsController < ApplicationController
|
|||
source_type
|
||||
source_full_path
|
||||
destination_name
|
||||
destination_slug
|
||||
destination_namespace
|
||||
]
|
||||
end
|
||||
|
|
|
@ -200,7 +200,7 @@ module Types
|
|||
if id
|
||||
pipeline.statuses.id_in(id.model_id)
|
||||
else
|
||||
pipeline.statuses.by_name(name)
|
||||
pipeline.latest_statuses.by_name(name)
|
||||
end.take # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
|
|
|
@ -55,6 +55,8 @@ class BulkImports::Entity < ApplicationRecord
|
|||
scope :by_bulk_import_id, ->(bulk_import_id) { where(bulk_import_id: bulk_import_id)}
|
||||
scope :order_by_created_at, -> (direction) { order(created_at: direction) }
|
||||
|
||||
alias_attribute :destination_slug, :destination_name
|
||||
|
||||
state_machine :status, initial: :created do
|
||||
state :created, value: 0
|
||||
state :started, value: 1
|
||||
|
|
|
@ -56,8 +56,10 @@ class Environment < ApplicationRecord
|
|||
|
||||
validates :external_url,
|
||||
length: { maximum: 255 },
|
||||
allow_nil: true,
|
||||
addressable_url: true
|
||||
allow_nil: true
|
||||
|
||||
validates :external_url, addressable_url: true, allow_nil: true, unless: :soft_validation_on_external_url_enabled?
|
||||
validate :safe_external_url, if: :soft_validation_on_external_url_enabled?
|
||||
|
||||
delegate :manual_actions, :other_manual_actions, to: :last_deployment, allow_nil: true
|
||||
delegate :auto_rollback_enabled?, to: :project
|
||||
|
@ -493,6 +495,26 @@ class Environment < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def soft_validation_on_external_url_enabled?
|
||||
::Feature.enabled?(:soft_validation_on_external_url, project)
|
||||
end
|
||||
|
||||
# We deliberately avoid using AddressableUrlValidator to allow users to update their environments even if they have
|
||||
# misconfigured `environment:url` keyword. The external URL is presented as a clickable link on UI and not consumed
|
||||
# in GitLab internally, thus we sanitize the URL before the persistence to make sure the rendered link is XSS safe.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/337417
|
||||
def safe_external_url
|
||||
return unless self.external_url.present?
|
||||
|
||||
new_external_url = Addressable::URI.parse(self.external_url)
|
||||
|
||||
if Gitlab::Utils::SanitizeNodeLink::UNSAFE_PROTOCOLS.include?(new_external_url.normalized_scheme)
|
||||
errors.add(:external_url, "#{new_external_url.normalized_scheme} scheme is not allowed")
|
||||
end
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
errors.add(:external_url, 'URI is invalid')
|
||||
end
|
||||
|
||||
def rollout_status_available?
|
||||
has_terminals?
|
||||
end
|
||||
|
|
|
@ -7,7 +7,6 @@ class GroupMember < Member
|
|||
|
||||
SOURCE_TYPE = 'Namespace'
|
||||
SOURCE_TYPE_FORMAT = /\ANamespace\z/.freeze
|
||||
THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS = 1000
|
||||
|
||||
belongs_to :group, foreign_key: 'source_id'
|
||||
alias_attribute :namespace_id, :source_id
|
||||
|
@ -67,28 +66,8 @@ class GroupMember < Member
|
|||
# its projects are also destroyed, so the removal of project_authorizations
|
||||
# will happen behind the scenes via DB foreign keys anyway.
|
||||
return if destroyed_by_association.present?
|
||||
return unless user_id
|
||||
return super if Feature.disabled?(:refresh_authorizations_via_affected_projects_on_group_membership, group)
|
||||
|
||||
# rubocop:disable CodeReuse/ServiceClass
|
||||
projects_to_refresh = Groups::ProjectsRequiringAuthorizationsRefresh::OnDirectMembershipFinder.new(group).execute
|
||||
threshold_exceeded = (projects_to_refresh.size > THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS)
|
||||
|
||||
# We want to try the new approach only if the number of affected projects are greater than the set threshold.
|
||||
return super unless threshold_exceeded
|
||||
|
||||
AuthorizedProjectUpdate::ProjectAccessChangedService
|
||||
.new(projects_to_refresh)
|
||||
.execute(blocking: false)
|
||||
|
||||
# Until we compare the inconsistency rates of the new approach
|
||||
# the old approach, we still run AuthorizedProjectsWorker
|
||||
# but with some delay and lower urgency as a safety net.
|
||||
UserProjectAccessChangedService
|
||||
.new(user_id)
|
||||
.execute(blocking: false, priority: UserProjectAccessChangedService::LOW_PRIORITY)
|
||||
|
||||
# rubocop:enable CodeReuse/ServiceClass
|
||||
super
|
||||
end
|
||||
|
||||
def send_invite
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
module Boards
|
||||
class DestroyService < Boards::BaseService
|
||||
def execute(board)
|
||||
if boards.size == 1
|
||||
return ServiceResponse.error(message: "The board could not be deleted, because the parent doesn't have any other boards.")
|
||||
end
|
||||
|
||||
board.destroy!
|
||||
|
||||
ServiceResponse.success
|
||||
|
|
|
@ -64,7 +64,7 @@ module BulkImports
|
|||
bulk_import: bulk_import,
|
||||
source_type: entity[:source_type],
|
||||
source_full_path: entity[:source_full_path],
|
||||
destination_name: entity[:destination_name],
|
||||
destination_slug: entity[:destination_slug],
|
||||
destination_namespace: entity[:destination_namespace]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: refresh_authorizations_via_affected_projects_on_group_membership
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87071
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362204
|
||||
milestone: '15.0'
|
||||
name: soft_validation_on_external_url
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91970
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367206
|
||||
milestone: '15.2'
|
||||
type: development
|
||||
group: group::workspace
|
||||
group: group::release
|
||||
default_enabled: false
|
|
@ -75,11 +75,12 @@ Email is processed correctly when a configured email address is present in one o
|
|||
(sorted in the order they are checked):
|
||||
|
||||
- `To`
|
||||
- `References`
|
||||
- `Delivered-To`
|
||||
- `Envelope-To` or `X-Envelope-To`
|
||||
- `Received`
|
||||
|
||||
The `References` header is also accepted, however it is used specifically to relate email responses to existing discussion threads. It is not used for creating issues by email.
|
||||
|
||||
In GitLab 14.6 and later, [Service Desk](../user/project/service_desk.md)
|
||||
also checks accepted headers.
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ POST /bulk_imports
|
|||
| `entities` | Array | yes | List of entities to import. |
|
||||
| `entities[source_type]` | String | yes | Source entity type (only `group_entity` is supported). |
|
||||
| `entities[source_full_path]` | String | yes | Source full path of the entity to import. |
|
||||
| `entities[destination_name]` | String | yes | Destination slug for the entity. |
|
||||
| `entities[destination_name]` | String | yes | Deprecated: Use :destination_slug instead. Destination slug for the entity. |
|
||||
| `entities[destination_slug]` | String | yes | Destination slug for the entity. |
|
||||
| `entities[destination_namespace]` | String | no | Destination namespace for the entity. |
|
||||
|
||||
```shell
|
||||
|
@ -41,7 +42,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
|
|||
{
|
||||
"source_full_path": "source/full/path",
|
||||
"source_type": "group_entity",
|
||||
"destination_name": "destination_slug",
|
||||
"destination_slug": "destination_slug",
|
||||
"destination_namespace": "destination/namespace/path"
|
||||
}
|
||||
]
|
||||
|
@ -126,7 +127,7 @@ curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
|
|||
"bulk_import_id": 1,
|
||||
"status": "finished",
|
||||
"source_full_path": "source_group",
|
||||
"destination_name": "destination_slug",
|
||||
"destination_slug": "destination_slug",
|
||||
"destination_namespace": "destination_path",
|
||||
"parent_id": null,
|
||||
"namespace_id": 1,
|
||||
|
@ -140,7 +141,7 @@ curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
|
|||
"bulk_import_id": 2,
|
||||
"status": "failed",
|
||||
"source_full_path": "another_group",
|
||||
"destination_name": "another_slug",
|
||||
"destination_slug": "another_slug",
|
||||
"destination_namespace": "another_namespace",
|
||||
"parent_id": null,
|
||||
"namespace_id": null,
|
||||
|
|
|
@ -12662,9 +12662,11 @@ Represents the Geo sync and verification state of a group wiki repository.
|
|||
| <a id="groupwikirepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the GroupWikiRepositoryRegistry. |
|
||||
| <a id="groupwikirepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the GroupWikiRepositoryRegistry. |
|
||||
| <a id="groupwikirepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the GroupWikiRepositoryRegistry. |
|
||||
| <a id="groupwikirepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the GroupWikiRepositoryRegistry should be resynced. |
|
||||
| <a id="groupwikirepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the GroupWikiRepositoryRegistry is resynced. |
|
||||
| <a id="groupwikirepositoryregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the GroupWikiRepositoryRegistry. |
|
||||
| <a id="groupwikirepositoryregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the GroupWikiRepositoryRegistry. |
|
||||
| <a id="groupwikirepositoryregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the GroupWikiRepositoryRegistry is reverified. |
|
||||
| <a id="groupwikirepositoryregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the GroupWikiRepositoryRegistry. |
|
||||
|
||||
### `HelmFileMetadata`
|
||||
|
||||
|
@ -13111,9 +13113,11 @@ Represents the Geo replication and verification state of a job_artifact.
|
|||
| <a id="jobartifactregistryid"></a>`id` | [`ID!`](#id) | ID of the JobArtifactRegistry. |
|
||||
| <a id="jobartifactregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the JobArtifactRegistry. |
|
||||
| <a id="jobartifactregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the JobArtifactRegistry. |
|
||||
| <a id="jobartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the JobArtifactRegistry should be resynced. |
|
||||
| <a id="jobartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the JobArtifactRegistry is resynced. |
|
||||
| <a id="jobartifactregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the JobArtifactRegistry. |
|
||||
| <a id="jobartifactregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the JobArtifactRegistry. |
|
||||
| <a id="jobartifactregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the JobArtifactRegistry is reverified. |
|
||||
| <a id="jobartifactregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the JobArtifactRegistry. |
|
||||
|
||||
### `JobPermissions`
|
||||
|
||||
|
@ -13163,9 +13167,11 @@ Represents the Geo sync and verification state of an LFS object.
|
|||
| <a id="lfsobjectregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the LfsObjectRegistry. |
|
||||
| <a id="lfsobjectregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the LfsObjectRegistry. |
|
||||
| <a id="lfsobjectregistrylfsobjectid"></a>`lfsObjectId` | [`ID!`](#id) | ID of the LFS object. |
|
||||
| <a id="lfsobjectregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry should be resynced. |
|
||||
| <a id="lfsobjectregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry is resynced. |
|
||||
| <a id="lfsobjectregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the LfsObjectRegistry. |
|
||||
| <a id="lfsobjectregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the LfsObjectRegistry. |
|
||||
| <a id="lfsobjectregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry is reverified. |
|
||||
| <a id="lfsobjectregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the LfsObjectRegistry. |
|
||||
|
||||
### `LicenseHistoryEntry`
|
||||
|
||||
|
@ -13847,9 +13853,11 @@ Represents the Geo sync and verification state of a Merge Request diff.
|
|||
| <a id="mergerequestdiffregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the MergeRequestDiffRegistry. |
|
||||
| <a id="mergerequestdiffregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the MergeRequestDiffRegistry. |
|
||||
| <a id="mergerequestdiffregistrymergerequestdiffid"></a>`mergeRequestDiffId` | [`ID!`](#id) | ID of the Merge Request diff. |
|
||||
| <a id="mergerequestdiffregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the MergeRequestDiffRegistry should be resynced. |
|
||||
| <a id="mergerequestdiffregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the MergeRequestDiffRegistry is resynced. |
|
||||
| <a id="mergerequestdiffregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the MergeRequestDiffRegistry. |
|
||||
| <a id="mergerequestdiffregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the MergeRequestDiffRegistry. |
|
||||
| <a id="mergerequestdiffregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the MergeRequestDiffRegistry is reverified. |
|
||||
| <a id="mergerequestdiffregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the MergeRequestDiffRegistry. |
|
||||
|
||||
### `MergeRequestParticipant`
|
||||
|
||||
|
@ -14794,9 +14802,11 @@ Represents the Geo sync and verification state of a package file.
|
|||
| <a id="packagefileregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PackageFileRegistry. |
|
||||
| <a id="packagefileregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PackageFileRegistry. |
|
||||
| <a id="packagefileregistrypackagefileid"></a>`packageFileId` | [`ID!`](#id) | ID of the PackageFile. |
|
||||
| <a id="packagefileregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PackageFileRegistry should be resynced. |
|
||||
| <a id="packagefileregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PackageFileRegistry is resynced. |
|
||||
| <a id="packagefileregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PackageFileRegistry. |
|
||||
| <a id="packagefileregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PackageFileRegistry. |
|
||||
| <a id="packagefileregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the PackageFileRegistry is reverified. |
|
||||
| <a id="packagefileregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the PackageFileRegistry. |
|
||||
|
||||
### `PackageHelmDependencyType`
|
||||
|
||||
|
@ -14916,9 +14926,11 @@ Represents the Geo replication and verification state of a pages_deployment.
|
|||
| <a id="pagesdeploymentregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PagesDeploymentRegistry. |
|
||||
| <a id="pagesdeploymentregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PagesDeploymentRegistry. |
|
||||
| <a id="pagesdeploymentregistrypagesdeploymentid"></a>`pagesDeploymentId` | [`ID!`](#id) | ID of the Pages Deployment. |
|
||||
| <a id="pagesdeploymentregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PagesDeploymentRegistry should be resynced. |
|
||||
| <a id="pagesdeploymentregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PagesDeploymentRegistry is resynced. |
|
||||
| <a id="pagesdeploymentregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PagesDeploymentRegistry. |
|
||||
| <a id="pagesdeploymentregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PagesDeploymentRegistry. |
|
||||
| <a id="pagesdeploymentregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the PagesDeploymentRegistry is reverified. |
|
||||
| <a id="pagesdeploymentregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the PagesDeploymentRegistry. |
|
||||
|
||||
### `PathLock`
|
||||
|
||||
|
@ -15085,9 +15097,11 @@ Represents the Geo sync and verification state of a pipeline artifact.
|
|||
| <a id="pipelineartifactregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PipelineArtifactRegistry. |
|
||||
| <a id="pipelineartifactregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PipelineArtifactRegistry. |
|
||||
| <a id="pipelineartifactregistrypipelineartifactid"></a>`pipelineArtifactId` | [`ID!`](#id) | ID of the pipeline artifact. |
|
||||
| <a id="pipelineartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PipelineArtifactRegistry should be resynced. |
|
||||
| <a id="pipelineartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PipelineArtifactRegistry is resynced. |
|
||||
| <a id="pipelineartifactregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PipelineArtifactRegistry. |
|
||||
| <a id="pipelineartifactregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PipelineArtifactRegistry. |
|
||||
| <a id="pipelineartifactregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the PipelineArtifactRegistry is reverified. |
|
||||
| <a id="pipelineartifactregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the PipelineArtifactRegistry. |
|
||||
|
||||
### `PipelineCounts`
|
||||
|
||||
|
@ -17252,10 +17266,12 @@ Represents the Geo sync and verification state of a snippet repository.
|
|||
| <a id="snippetrepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the SnippetRepositoryRegistry. |
|
||||
| <a id="snippetrepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the SnippetRepositoryRegistry. |
|
||||
| <a id="snippetrepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the SnippetRepositoryRegistry. |
|
||||
| <a id="snippetrepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the SnippetRepositoryRegistry should be resynced. |
|
||||
| <a id="snippetrepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the SnippetRepositoryRegistry is resynced. |
|
||||
| <a id="snippetrepositoryregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the SnippetRepositoryRegistry. |
|
||||
| <a id="snippetrepositoryregistrysnippetrepositoryid"></a>`snippetRepositoryId` | [`ID!`](#id) | ID of the Snippet Repository. |
|
||||
| <a id="snippetrepositoryregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the SnippetRepositoryRegistry. |
|
||||
| <a id="snippetrepositoryregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the SnippetRepositoryRegistry is reverified. |
|
||||
| <a id="snippetrepositoryregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the SnippetRepositoryRegistry. |
|
||||
|
||||
### `StatusAction`
|
||||
|
||||
|
@ -17354,10 +17370,12 @@ Represents the Geo sync and verification state of a terraform state version.
|
|||
| <a id="terraformstateversionregistryid"></a>`id` | [`ID!`](#id) | ID of the TerraformStateVersionRegistry. |
|
||||
| <a id="terraformstateversionregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the TerraformStateVersionRegistry. |
|
||||
| <a id="terraformstateversionregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the TerraformStateVersionRegistry. |
|
||||
| <a id="terraformstateversionregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the TerraformStateVersionRegistry should be resynced. |
|
||||
| <a id="terraformstateversionregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the TerraformStateVersionRegistry is resynced. |
|
||||
| <a id="terraformstateversionregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the TerraformStateVersionRegistry. |
|
||||
| <a id="terraformstateversionregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the TerraformStateVersionRegistry. |
|
||||
| <a id="terraformstateversionregistryterraformstateversionid"></a>`terraformStateVersionId` | [`ID!`](#id) | ID of the terraform state version. |
|
||||
| <a id="terraformstateversionregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the TerraformStateVersionRegistry is reverified. |
|
||||
| <a id="terraformstateversionregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the TerraformStateVersionRegistry. |
|
||||
|
||||
### `TestCase`
|
||||
|
||||
|
@ -17618,9 +17636,11 @@ Represents the Geo replication and verification state of an upload.
|
|||
| <a id="uploadregistryid"></a>`id` | [`ID!`](#id) | ID of the UploadRegistry. |
|
||||
| <a id="uploadregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the UploadRegistry. |
|
||||
| <a id="uploadregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the UploadRegistry. |
|
||||
| <a id="uploadregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the UploadRegistry should be resynced. |
|
||||
| <a id="uploadregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the UploadRegistry is resynced. |
|
||||
| <a id="uploadregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the UploadRegistry. |
|
||||
| <a id="uploadregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the UploadRegistry. |
|
||||
| <a id="uploadregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the UploadRegistry is reverified. |
|
||||
| <a id="uploadregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the UploadRegistry. |
|
||||
|
||||
### `UsageTrendsMeasurement`
|
||||
|
||||
|
|
|
@ -374,6 +374,8 @@ To retry or rollback a deployment:
|
|||
|
||||
### Environment URL
|
||||
|
||||
> [Fixed](https://gitlab.com/gitlab-org/gitlab/-/issues/337417) to persist arbitrary URLs in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `soft_validation_on_external_url`. Disabled by default.
|
||||
|
||||
The [environment URL](../yaml/index.md#environmenturl) is displayed in a few
|
||||
places in GitLab:
|
||||
|
||||
|
|
|
@ -47,11 +47,23 @@ module API
|
|||
requires :source_type, type: String, desc: 'Source entity type (only `group_entity` is supported)',
|
||||
values: %w[group_entity]
|
||||
requires :source_full_path, type: String, desc: 'Source full path of the entity to import'
|
||||
requires :destination_name, type: String, desc: 'Destination slug for the entity'
|
||||
requires :destination_namespace, type: String, desc: 'Destination namespace for the entity'
|
||||
optional :destination_slug, type: String, desc: 'Destination slug for the entity'
|
||||
optional :destination_name, type: String,
|
||||
desc: 'Deprecated: Use :destination_slug instead. Destination slug for the entity'
|
||||
|
||||
mutually_exclusive :destination_slug, :destination_name
|
||||
at_least_one_of :destination_slug, :destination_name
|
||||
end
|
||||
end
|
||||
post do
|
||||
params[:entities].each do |entity|
|
||||
if entity[:destination_name]
|
||||
entity[:destination_slug] ||= entity[:destination_name]
|
||||
entity.delete(:destination_name)
|
||||
end
|
||||
end
|
||||
|
||||
response = ::BulkImports::CreateService.new(
|
||||
current_user,
|
||||
params[:entities],
|
||||
|
|
|
@ -8,7 +8,8 @@ module API
|
|||
expose :bulk_import_id
|
||||
expose :status_name, as: :status
|
||||
expose :source_full_path
|
||||
expose :destination_name
|
||||
expose :destination_name # deprecated
|
||||
expose :destination_slug
|
||||
expose :destination_namespace
|
||||
expose :parent_id
|
||||
expose :namespace_id
|
||||
|
|
|
@ -38,7 +38,7 @@ module BulkImports
|
|||
end
|
||||
|
||||
def transform_path(import_entity, data)
|
||||
data['path'] = import_entity.destination_name.parameterize
|
||||
data['path'] = import_entity.destination_slug.parameterize
|
||||
data
|
||||
end
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ module BulkImports
|
|||
entity = context.entity
|
||||
visibility = data.delete('visibility')
|
||||
|
||||
project[:name] = entity.destination_name
|
||||
project[:path] = entity.destination_name.parameterize
|
||||
project[:name] = entity.destination_slug
|
||||
project[:path] = entity.destination_slug.parameterize
|
||||
project[:created_at] = data['created_at']
|
||||
project[:import_type] = PROJECT_IMPORT_TYPE
|
||||
project[:visibility_level] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?
|
||||
|
|
|
@ -612,8 +612,8 @@ RSpec.describe Admin::UsersController do
|
|||
end
|
||||
|
||||
context 'when the new password does not match the password confirmation' do
|
||||
let(:password) { 'some_password' }
|
||||
let(:password_confirmation) { 'not_same_as_password' }
|
||||
let(:password) { User.random_password }
|
||||
let(:password_confirmation) { User.random_password }
|
||||
|
||||
it 'shows the edit page again' do
|
||||
update_password(user, password, password_confirmation)
|
||||
|
|
|
@ -245,11 +245,11 @@ RSpec.describe Import::BulkImportsController do
|
|||
let(:bulk_import_params) do
|
||||
[{ "source_type" => "group_entity",
|
||||
"source_full_path" => "full_path",
|
||||
"destination_name" => "destination_name",
|
||||
"destination_slug" => "destination_name",
|
||||
"destination_namespace" => "root" },
|
||||
{ "source_type" => "group_entity2",
|
||||
"source_full_path" => "full_path2",
|
||||
"destination_name" => "destination_name2",
|
||||
"destination_slug" => "destination_name2",
|
||||
"destination_namespace" => "root" }]
|
||||
end
|
||||
|
||||
|
@ -258,7 +258,7 @@ RSpec.describe Import::BulkImportsController do
|
|||
session[:bulk_import_gitlab_url] = instance_url
|
||||
end
|
||||
|
||||
it 'executes BulkImpors::CreateService' do
|
||||
it 'executes BulkImports::CreateService' do
|
||||
error_response = ServiceResponse.error(message: 'Record invalid', http_status: :unprocessable_entity)
|
||||
|
||||
expect_next_instance_of(
|
||||
|
@ -276,6 +276,38 @@ RSpec.describe Import::BulkImportsController do
|
|||
expect(json_response).to eq([{ "success" => true, "id" => bulk_import.id, "message" => nil },
|
||||
{ "success" => false, "id" => nil, "message" => "Record invalid" }])
|
||||
end
|
||||
|
||||
context 'when entity destination_name is specified' do
|
||||
let(:bulk_import_params) do
|
||||
[
|
||||
{
|
||||
"source_type" => "group_entity",
|
||||
"source_full_path" => "full_path",
|
||||
"destination_name" => "destination_name",
|
||||
"destination_namespace" => "root"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it 'replaces destination_name with destination_slug and executes BulkImports::CreateService' do
|
||||
entity = {
|
||||
"source_type" => "group_entity",
|
||||
"source_full_path" => "full_path",
|
||||
"destination_slug" => "destination_name",
|
||||
"destination_namespace" => "root"
|
||||
}
|
||||
|
||||
expect_next_instance_of(
|
||||
::BulkImports::CreateService, user, entity, { url: instance_url, access_token: pat }) do |service|
|
||||
allow(service).to receive(:execute).and_return(ServiceResponse.success(payload: bulk_import))
|
||||
end
|
||||
|
||||
post :create, params: { bulk_import: bulk_import_params }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to match_array([{ "success" => true, "id" => bulk_import.id, "message" => nil }])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
require('spec_helper')
|
||||
|
||||
RSpec.describe ProfilesController, :request_store do
|
||||
let(:password) { 'longsecret987!' }
|
||||
let(:password) { User.random_password }
|
||||
let(:user) { create(:user, password: password) }
|
||||
|
||||
describe 'POST update' do
|
||||
it 'does not update password' do
|
||||
sign_in(user)
|
||||
|
||||
new_password = User.random_password
|
||||
expect do
|
||||
post :update,
|
||||
params: { user: { password: 'hello12345', password_confirmation: 'hello12345' } }
|
||||
params: { user: { password: new_password, password_confirmation: new_password } }
|
||||
end.not_to change { user.reload.encrypted_password }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
|
|
|
@ -233,7 +233,7 @@ RSpec.describe Projects::EnvironmentsController do
|
|||
end
|
||||
|
||||
context "when environment params are invalid" do
|
||||
let(:params) { environment_params.merge(environment: { name: '/foo/', external_url: '/git.gitlab.com' }) }
|
||||
let(:params) { environment_params.merge(environment: { external_url: 'javascript:alert("hello")' }) }
|
||||
|
||||
it 'returns bad request' do
|
||||
subject
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe RegistrationsController do
|
|||
end
|
||||
|
||||
let_it_be(:base_user_params) do
|
||||
{ first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: 'Any_password' }
|
||||
{ first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: User.random_password }
|
||||
end
|
||||
|
||||
let_it_be(:user_params) { { user: base_user_params } }
|
||||
|
@ -222,7 +222,7 @@ RSpec.describe RegistrationsController do
|
|||
context 'when the registration fails' do
|
||||
let_it_be(:member) { create(:project_member, :invited) }
|
||||
let_it_be(:missing_user_params) do
|
||||
{ username: '', email: member.invite_email, password: 'Any_password' }
|
||||
{ username: '', email: member.invite_email, password: User.random_password }
|
||||
end
|
||||
|
||||
let_it_be(:user_params) { { user: missing_user_params } }
|
||||
|
@ -535,7 +535,7 @@ RSpec.describe RegistrationsController do
|
|||
end
|
||||
|
||||
it 'succeeds if password is confirmed' do
|
||||
post :destroy, params: { password: '12345678' }
|
||||
post :destroy, params: { password: user.password }
|
||||
|
||||
expect_success
|
||||
end
|
||||
|
@ -576,7 +576,7 @@ RSpec.describe RegistrationsController do
|
|||
end
|
||||
|
||||
it 'fails' do
|
||||
delete :destroy, params: { password: '12345678' }
|
||||
delete :destroy, params: { password: user.password }
|
||||
|
||||
expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
|
||||
end
|
||||
|
|
|
@ -19,7 +19,6 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
|
|||
|
||||
project.add_developer(user)
|
||||
sign_in(user)
|
||||
stub_const('Gitlab::QueryLimiting::Transaction::THRESHOLD', 104)
|
||||
end
|
||||
|
||||
context 'when hovering over a parallel view diff file' do
|
||||
|
|
|
@ -477,14 +477,14 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
end
|
||||
|
||||
context 'with invalid username and password' do
|
||||
let(:user) { create(:user, password: 'not-the-default') }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'blocks invalid login' do
|
||||
expect(authentication_metrics)
|
||||
.to increment(:user_unauthenticated_counter)
|
||||
.and increment(:user_password_invalid_counter)
|
||||
|
||||
gitlab_sign_in(user)
|
||||
gitlab_sign_in(user, password: 'incorrect-password')
|
||||
|
||||
expect_single_session_with_short_ttl
|
||||
expect(page).to have_content('Invalid login or password.')
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import packageJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/package_json_linker';
|
||||
import gemspecLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker';
|
||||
import linkDependencies from '~/vue_shared/components/source_viewer/plugins/link_dependencies';
|
||||
import { PACKAGE_JSON_FILE_TYPE, PACKAGE_JSON_CONTENT } from './mock_data';
|
||||
import { PACKAGE_JSON_FILE_TYPE, PACKAGE_JSON_CONTENT, GEMSPEC_FILE_TYPE } from './mock_data';
|
||||
|
||||
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/package_json_linker');
|
||||
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker');
|
||||
|
||||
describe('Highlight.js plugin for linking dependencies', () => {
|
||||
const hljsResultMock = { value: 'test' };
|
||||
|
@ -11,4 +13,9 @@ describe('Highlight.js plugin for linking dependencies', () => {
|
|||
linkDependencies(hljsResultMock, PACKAGE_JSON_FILE_TYPE, PACKAGE_JSON_CONTENT);
|
||||
expect(packageJsonLinker).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls gemspecLinker for gemspec file types', () => {
|
||||
linkDependencies(hljsResultMock, GEMSPEC_FILE_TYPE);
|
||||
expect(gemspecLinker).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
export const PACKAGE_JSON_FILE_TYPE = 'package_json';
|
||||
export const PACKAGE_JSON_CONTENT = '{ "dependencies": { "@babel/core": "^7.18.5" } }';
|
||||
|
||||
export const GEMSPEC_FILE_TYPE = 'gemspec';
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import gemspecLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker';
|
||||
|
||||
describe('Highlight.js plugin for linking gemspec dependencies', () => {
|
||||
it('mutates the input value by wrapping dependency names in anchors', () => {
|
||||
const inputValue =
|
||||
's.add_dependency(<span class="hljs-string">'rugged'</span>, <span class="hljs-string">'~> 0.24.0'</span>)';
|
||||
const outputValue =
|
||||
's.add_dependency(<span class="hljs-string linked">'<a href="https://rubygems.org/gems/rugged" rel="nofollow noreferrer noopener">rugged</a>'</span>, <span class="hljs-string">'~> 0.24.0'</span>)';
|
||||
const hljsResultMock = { value: inputValue };
|
||||
|
||||
const output = gemspecLinker(hljsResultMock);
|
||||
expect(output).toBe(outputValue);
|
||||
});
|
||||
});
|
|
@ -59,11 +59,9 @@ export const workItemQueryResponse = {
|
|||
title: 'Parent title',
|
||||
},
|
||||
children: {
|
||||
edges: [
|
||||
nodes: [
|
||||
{
|
||||
node: {
|
||||
id: 'gid://gitlab/WorkItem/444',
|
||||
},
|
||||
id: 'gid://gitlab/WorkItem/444',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -96,9 +94,9 @@ export const updateWorkItemMutationResponse = {
|
|||
widgets: [
|
||||
{
|
||||
children: {
|
||||
edges: [
|
||||
nodes: [
|
||||
{
|
||||
node: 'gid://gitlab/WorkItem/444',
|
||||
id: 'gid://gitlab/WorkItem/444',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -161,11 +159,9 @@ export const workItemResponseFactory = ({
|
|||
__typename: 'WorkItemWidgetHierarchy',
|
||||
type: 'HIERARCHY',
|
||||
children: {
|
||||
edges: [
|
||||
nodes: [
|
||||
{
|
||||
node: {
|
||||
id: 'gid://gitlab/WorkItem/444',
|
||||
},
|
||||
id: 'gid://gitlab/WorkItem/444',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@ RSpec.describe API::Entities::BulkImports::Entity do
|
|||
:status,
|
||||
:source_full_path,
|
||||
:destination_name,
|
||||
:destination_slug,
|
||||
:destination_namespace,
|
||||
:parent_id,
|
||||
:namespace_id,
|
||||
|
|
|
@ -13,7 +13,7 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
|
|||
:bulk_import_entity,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: 'destination-name-path',
|
||||
destination_slug: 'destination-slug-path',
|
||||
destination_namespace: parent.full_path
|
||||
)
|
||||
end
|
||||
|
@ -41,14 +41,14 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
|
|||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'parent_id' => parent.id,
|
||||
'path' => 'destination-name-path'
|
||||
'path' => 'destination-slug-path'
|
||||
})
|
||||
end
|
||||
|
||||
it 'transforms path from destination_name' do
|
||||
it 'transforms path from destination_slug' do
|
||||
transformed_data = subject.transform(context, data)
|
||||
|
||||
expect(transformed_data['path']).to eq(entity.destination_name)
|
||||
expect(transformed_data['path']).to eq(entity.destination_slug)
|
||||
end
|
||||
|
||||
it 'removes full path' do
|
||||
|
|
|
@ -15,7 +15,7 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
|
|||
source_type: :project_entity,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: 'Destination Project Name',
|
||||
destination_slug: 'Destination Project Name',
|
||||
destination_namespace: destination_group.full_path
|
||||
)
|
||||
end
|
||||
|
@ -32,12 +32,12 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
|
|||
|
||||
subject(:transformed_data) { described_class.new.transform(context, data) }
|
||||
|
||||
it 'transforms name to destination name' do
|
||||
expect(transformed_data[:name]).to eq(entity.destination_name)
|
||||
it 'transforms name to destination slug' do
|
||||
expect(transformed_data[:name]).to eq(entity.destination_slug)
|
||||
end
|
||||
|
||||
it 'adds path as parameterized name' do
|
||||
expect(transformed_data[:path]).to eq(entity.destination_name.parameterize)
|
||||
expect(transformed_data[:path]).to eq(entity.destination_slug.parameterize)
|
||||
end
|
||||
|
||||
it 'transforms visibility level' do
|
||||
|
@ -65,7 +65,7 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
|
|||
source_type: :project_entity,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: 'Destination Project Name',
|
||||
destination_slug: 'Destination Project Name',
|
||||
destination_namespace: ''
|
||||
)
|
||||
|
||||
|
|
|
@ -42,6 +42,92 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'validate and sanitize external url' do
|
||||
let_it_be_with_refind(:environment) { create(:environment) }
|
||||
|
||||
where(:source_external_url, :expected_error_message) do
|
||||
nil | nil
|
||||
'http://example.com' | nil
|
||||
'example.com' | nil
|
||||
'www.example.io' | nil
|
||||
'http://$URL' | nil
|
||||
'http://$(URL)' | nil
|
||||
'custom://example.com' | nil
|
||||
'1.1.1.1' | nil
|
||||
'$BASE_URL/${CI_COMMIT_REF_NAME}' | nil
|
||||
'$ENVIRONMENT_URL' | nil
|
||||
'https://$SUB.$MAIN' | nil
|
||||
'https://$SUB-$REGION.$MAIN' | nil
|
||||
'https://example.com?param={()}' | nil
|
||||
'http://XSS?x=<script>alert(1)</script>' | nil
|
||||
'https://user:${VARIABLE}@example.io' | nil
|
||||
'https://example.com/test?param={data}' | nil
|
||||
'http://${URL}' | 'URI is invalid'
|
||||
'https://${URL}.example/test' | 'URI is invalid'
|
||||
'http://test${CI_MERGE_REQUEST_IID}.example.com' | 'URI is invalid'
|
||||
'javascript:alert("hello")' | 'javascript scheme is not allowed'
|
||||
end
|
||||
with_them do
|
||||
it 'sets an external URL or an error' do
|
||||
environment.external_url = source_external_url
|
||||
|
||||
environment.valid?
|
||||
|
||||
if expected_error_message
|
||||
expect(environment.errors[:external_url].first).to eq(expected_error_message)
|
||||
else
|
||||
expect(environment.errors[:external_url]).to be_empty,
|
||||
"There were unexpected errors: #{environment.errors.full_messages}"
|
||||
expect(environment.external_url).to eq(source_external_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when soft_validation_on_external_url feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(soft_validation_on_external_url: false)
|
||||
end
|
||||
|
||||
where(:source_external_url, :expected_error_message) do
|
||||
nil | nil
|
||||
'http://example.com' | nil
|
||||
'example.com' | 'is blocked: Only allowed schemes are http, https'
|
||||
'www.example.io' | 'is blocked: Only allowed schemes are http, https'
|
||||
'http://$URL' | 'is blocked: Hostname or IP address invalid'
|
||||
'http://$(URL)' | 'is blocked: Hostname or IP address invalid'
|
||||
'custom://example.com' | 'is blocked: Only allowed schemes are http, https'
|
||||
'1.1.1.1' | 'is blocked: Only allowed schemes are http, https'
|
||||
'$BASE_URL/${CI_COMMIT_REF_NAME}' | 'is blocked: Only allowed schemes are http, https'
|
||||
'$ENVIRONMENT_URL' | 'is blocked: Only allowed schemes are http, https'
|
||||
'https://$SUB.$MAIN' | 'is blocked: Hostname or IP address invalid'
|
||||
'https://$SUB-$REGION.$MAIN' | 'is blocked: Hostname or IP address invalid'
|
||||
'https://example.com?param={()}' | nil
|
||||
'http://XSS?x=<script>alert(1)</script>' | nil
|
||||
'https://user:${VARIABLE}@example.io' | nil
|
||||
'https://example.com/test?param={data}' | nil
|
||||
'http://${URL}' | 'is blocked: URI is invalid'
|
||||
'https://${URL}.example/test' | 'is blocked: URI is invalid'
|
||||
'http://test${CI_MERGE_REQUEST_IID}.example.com' | 'is blocked: URI is invalid'
|
||||
'javascript:alert("hello")' | 'is blocked: Only allowed schemes are http, https'
|
||||
end
|
||||
with_them do
|
||||
it 'sets an external URL or an error' do
|
||||
environment.external_url = source_external_url
|
||||
|
||||
environment.valid?
|
||||
|
||||
if expected_error_message
|
||||
expect(environment.errors[:external_url].first).to eq(expected_error_message)
|
||||
else
|
||||
expect(environment.errors[:external_url]).to be_empty,
|
||||
"There were unexpected errors: #{environment.errors.full_messages}"
|
||||
expect(environment.external_url).to eq(source_external_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.before_save' do
|
||||
it 'ensures environment tier when a new object is created' do
|
||||
environment = build(:environment, name: 'gprd', tier: nil)
|
||||
|
|
|
@ -165,13 +165,6 @@ RSpec.describe GroupMember do
|
|||
let_it_be(:project_b) { create(:project, group: group) }
|
||||
let_it_be(:project_c) { create(:project, group: group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:affected_project_ids) { Project.id_in([project_a, project_b, project_c]).ids }
|
||||
|
||||
before do
|
||||
stub_const(
|
||||
"#{described_class.name}::THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS",
|
||||
affected_project_ids.size - 1)
|
||||
end
|
||||
|
||||
shared_examples_for 'calls UserProjectAccessChangedService to recalculate authorizations' do
|
||||
it 'calls UserProjectAccessChangedService to recalculate authorizations' do
|
||||
|
@ -183,41 +176,6 @@ RSpec.describe GroupMember do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'tries to update permissions via refreshing authorizations for the affected projects' do
|
||||
context 'when the number of affected projects exceeds the set threshold' do
|
||||
it 'updates permissions via refreshing authorizations for the affected projects asynchronously' do
|
||||
expect_next_instance_of(
|
||||
AuthorizedProjectUpdate::ProjectAccessChangedService, affected_project_ids
|
||||
) do |service|
|
||||
expect(service).to receive(:execute).with(blocking: false)
|
||||
end
|
||||
|
||||
action
|
||||
end
|
||||
|
||||
it 'calls AuthorizedProjectUpdate::UserRefreshFromReplicaWorker with a delay as a safety net' do
|
||||
expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
|
||||
receive(:bulk_perform_in)
|
||||
.with(1.hour,
|
||||
[[user.id]],
|
||||
batch_delay: 30.seconds, batch_size: 100)
|
||||
)
|
||||
|
||||
action
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the number of affected projects does not exceed the set threshold' do
|
||||
before do
|
||||
stub_const(
|
||||
"#{described_class.name}::THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS",
|
||||
affected_project_ids.size + 1)
|
||||
end
|
||||
|
||||
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
|
||||
end
|
||||
end
|
||||
|
||||
context 'on create' do
|
||||
let(:action) { group.add_member(user, Gitlab::Access::GUEST) }
|
||||
let(:blocking) { true }
|
||||
|
@ -228,15 +186,7 @@ RSpec.describe GroupMember do
|
|||
.and change { user.can?(:guest_access, project_c) }.from(false).to(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'tries to update permissions via refreshing authorizations for the affected projects'
|
||||
|
||||
context 'when the feature flag `refresh_authorizations_via_affected_projects_on_group_membership` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(refresh_authorizations_via_affected_projects_on_group_membership: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
|
||||
end
|
||||
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
|
||||
end
|
||||
|
||||
context 'on update' do
|
||||
|
@ -253,15 +203,7 @@ RSpec.describe GroupMember do
|
|||
.and change { user.can?(:developer_access, project_c) }.from(false).to(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'tries to update permissions via refreshing authorizations for the affected projects'
|
||||
|
||||
context 'when the feature flag `refresh_authorizations_via_affected_projects_on_group_membership` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(refresh_authorizations_via_affected_projects_on_group_membership: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
|
||||
end
|
||||
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
|
||||
end
|
||||
|
||||
context 'on destroy' do
|
||||
|
@ -278,15 +220,7 @@ RSpec.describe GroupMember do
|
|||
.and change { user.can?(:guest_access, project_c) }.from(true).to(false)
|
||||
end
|
||||
|
||||
it_behaves_like 'tries to update permissions via refreshing authorizations for the affected projects'
|
||||
|
||||
context 'when the feature flag `refresh_authorizations_via_affected_projects_on_group_membership` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(refresh_authorizations_via_affected_projects_on_group_membership: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
|
||||
end
|
||||
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,9 +57,11 @@ RSpec.describe API::Boards do
|
|||
let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}" }
|
||||
|
||||
it 'delete the issue board' do
|
||||
delete api(url, user)
|
||||
expect do
|
||||
delete api(url, user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end.to change {board_parent.boards.count}.by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -53,23 +53,80 @@ RSpec.describe API::BulkImports do
|
|||
end
|
||||
end
|
||||
|
||||
it 'starts a new migration' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
configuration: {
|
||||
url: 'http://gitlab.example',
|
||||
access_token: 'access_token'
|
||||
},
|
||||
entities: [
|
||||
source_type: 'group_entity',
|
||||
source_full_path: 'full_path',
|
||||
destination_name: 'destination_slug',
|
||||
destination_namespace: 'destination_namespace'
|
||||
]
|
||||
}
|
||||
shared_examples 'starting a new migration' do
|
||||
it 'starts a new migration' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
configuration: {
|
||||
url: 'http://gitlab.example',
|
||||
access_token: 'access_token'
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
source_type: 'group_entity',
|
||||
source_full_path: 'full_path',
|
||||
destination_namespace: 'destination_namespace'
|
||||
}.merge(destination_param)
|
||||
]
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
|
||||
expect(json_response['status']).to eq('created')
|
||||
expect(json_response['status']).to eq('created')
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'starting a new migration' do
|
||||
let(:destination_param) { { destination_slug: 'destination_slug' } }
|
||||
end
|
||||
|
||||
include_examples 'starting a new migration' do
|
||||
let(:destination_param) { { destination_name: 'destination_name' } }
|
||||
end
|
||||
|
||||
context 'when both destination_name & destination_slug are provided' do
|
||||
it 'returns a mutually exclusive error' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
configuration: {
|
||||
url: 'http://gitlab.example',
|
||||
access_token: 'access_token'
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
source_type: 'group_entity',
|
||||
source_full_path: 'full_path',
|
||||
destination_name: 'destination_name',
|
||||
destination_slug: 'destination_slug',
|
||||
destination_namespace: 'destination_namespace'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
|
||||
expect(json_response['error']).to eq('entities[0][destination_slug], entities[0][destination_name] are mutually exclusive')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when neither destination_name nor destination_slug is provided' do
|
||||
it 'returns at_least_one_of error' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
configuration: {
|
||||
url: 'http://gitlab.example',
|
||||
access_token: 'access_token'
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
source_type: 'group_entity',
|
||||
source_full_path: 'full_path',
|
||||
destination_namespace: 'destination_namespace'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
|
||||
expect(json_response['error']).to eq('entities[0][destination_slug], entities[0][destination_name] are missing, at least one parameter must be provided')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided url is blocked' do
|
||||
|
@ -82,7 +139,7 @@ RSpec.describe API::BulkImports do
|
|||
entities: [
|
||||
source_type: 'group_entity',
|
||||
source_full_path: 'full_path',
|
||||
destination_name: 'destination_slug',
|
||||
destination_slug: 'destination_slug',
|
||||
destination_namespace: 'destination_namespace'
|
||||
]
|
||||
}
|
||||
|
|
|
@ -166,6 +166,35 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.job' do
|
||||
let(:first_n) { var('Int') }
|
||||
let(:query_path) do
|
||||
[
|
||||
[:project, { full_path: project.full_path }],
|
||||
[:pipelines],
|
||||
[:nodes],
|
||||
[:job, { name: 'Job 1' }]
|
||||
]
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
wrap_fields(query_graphql_path(query_path, :status))
|
||||
end
|
||||
|
||||
before_all do
|
||||
pipeline = create(:ci_pipeline, project: project)
|
||||
create(:ci_build, pipeline: pipeline, name: 'Job 1', status: :failed, retried: true)
|
||||
create(:ci_build, pipeline: pipeline, name: 'Job 1', status: :success)
|
||||
end
|
||||
|
||||
it 'fetches the latest job with the given name' do
|
||||
post_graphql(query, current_user: user)
|
||||
expect(graphql_data_at(*query_path.map(&:first))).to contain_exactly a_hash_including(
|
||||
'status' => 'SUCCESS'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.jobs' do
|
||||
let(:first_n) { var('Int') }
|
||||
let(:query_path) do
|
||||
|
|
|
@ -65,15 +65,8 @@ RSpec.describe Mutations::Boards::Destroy do
|
|||
other_board.destroy!
|
||||
end
|
||||
|
||||
it 'does not destroy the board' do
|
||||
expect { subject }.not_to change { Board.count }.from(1)
|
||||
end
|
||||
|
||||
it 'returns an error and not nil board' do
|
||||
subject
|
||||
|
||||
expect(mutation_response['errors']).not_to be_empty
|
||||
expect(mutation_response['board']).not_to be_nil
|
||||
it 'does destroy the board' do
|
||||
expect { subject }.to change { Board.count }.by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,19 +10,19 @@ RSpec.describe BulkImports::CreateService do
|
|||
{
|
||||
source_type: 'group_entity',
|
||||
source_full_path: 'full/path/to/group1',
|
||||
destination_name: 'destination group 1',
|
||||
destination_slug: 'destination group 1',
|
||||
destination_namespace: 'full/path/to/destination1'
|
||||
},
|
||||
{
|
||||
source_type: 'group_entity',
|
||||
source_full_path: 'full/path/to/group2',
|
||||
destination_name: 'destination group 2',
|
||||
destination_slug: 'destination group 2',
|
||||
destination_namespace: 'full/path/to/destination2'
|
||||
},
|
||||
{
|
||||
source_type: 'project_entity',
|
||||
source_full_path: 'full/path/to/project1',
|
||||
destination_name: 'destination project 1',
|
||||
destination_slug: 'destination project 1',
|
||||
destination_namespace: 'full/path/to/destination1'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -112,7 +112,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
|
|||
end
|
||||
|
||||
context 'when external URL is invalid' do
|
||||
let(:external_url) { 'google.com' }
|
||||
let(:external_url) { 'javascript:alert("hello")' }
|
||||
|
||||
it 'fails to update the tier due to validation error' do
|
||||
expect { subject.execute }.not_to change { environment.tier }
|
||||
|
@ -123,7 +123,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
|
|||
.with(an_instance_of(described_class::EnvironmentUpdateFailure),
|
||||
project_id: project.id,
|
||||
environment_id: environment.id,
|
||||
reason: %q{External url is blocked: Only allowed schemes are http, https})
|
||||
reason: %q{External url javascript scheme is not allowed})
|
||||
.once
|
||||
|
||||
subject.execute
|
||||
|
|
|
@ -91,12 +91,12 @@ module LoginHelpers
|
|||
# user - User instance to login with
|
||||
# remember - Whether or not to check "Remember me" (default: false)
|
||||
# two_factor_auth - If two-factor authentication is enabled (default: false)
|
||||
# password - password to attempt to login with
|
||||
# password - password to attempt to login with (default: user.password)
|
||||
def gitlab_sign_in_with(user, remember: false, two_factor_auth: false, password: nil)
|
||||
visit new_user_session_path
|
||||
|
||||
fill_in "user_login", with: user.email
|
||||
fill_in "user_password", with: (password || "12345678")
|
||||
fill_in "user_password", with: (password || user.password)
|
||||
check 'user_remember_me' if remember
|
||||
|
||||
find('[data-testid="sign-in-button"]:enabled').click
|
||||
|
|
|
@ -20,10 +20,10 @@ RSpec.shared_examples 'board destroy service' do
|
|||
end
|
||||
|
||||
context 'when there is only one board' do
|
||||
it 'does not remove board' do
|
||||
it 'does remove board' do
|
||||
expect do
|
||||
expect(service.execute(board)).to be_error
|
||||
end.not_to change(boards, :count)
|
||||
service.execute(board)
|
||||
end.to change(boards, :count).by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue