Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ae845b6277
commit
0c8b3354d9
|
@ -7,10 +7,11 @@ export const parsedData = (state) => {
|
|||
state.chartData.forEach(({ date, author_name, author_email }) => {
|
||||
total[date] = total[date] ? total[date] + 1 : 1;
|
||||
|
||||
const authorData = byAuthorEmail[author_email];
|
||||
const normalizedEmail = author_email.toLowerCase();
|
||||
const authorData = byAuthorEmail[normalizedEmail];
|
||||
|
||||
if (!authorData) {
|
||||
byAuthorEmail[author_email] = {
|
||||
byAuthorEmail[normalizedEmail] = {
|
||||
name: author_name,
|
||||
commits: 1,
|
||||
dates: {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module WorkItems
|
||||
class CreateFromTask < BaseMutation
|
||||
include Mutations::SpamProtection
|
||||
|
||||
description "Creates a work item from a task in another work item's description." \
|
||||
" Available only when feature flag `work_items` is enabled. This feature is experimental and is subject to change without notice."
|
||||
|
||||
graphql_name 'WorkItemCreateFromTask'
|
||||
|
||||
authorize :update_work_item
|
||||
|
||||
argument :id, ::Types::GlobalIDType[::WorkItem],
|
||||
required: true,
|
||||
description: 'Global ID of the work item.'
|
||||
argument :work_item_data, ::Types::WorkItems::ConvertTaskInputType,
|
||||
required: true,
|
||||
description: 'Arguments necessary to convert a task into a work item.',
|
||||
prepare: ->(attributes, _ctx) { attributes.to_h }
|
||||
|
||||
field :work_item, Types::WorkItemType,
|
||||
null: true,
|
||||
description: 'Updated work item.'
|
||||
|
||||
field :new_work_item, Types::WorkItemType,
|
||||
null: true,
|
||||
description: 'New work item created from task.'
|
||||
|
||||
def resolve(id:, work_item_data:)
|
||||
work_item = authorized_find!(id: id)
|
||||
|
||||
unless Feature.enabled?(:work_items, work_item.project)
|
||||
return { errors: ['`work_items` feature flag disabled for this project'] }
|
||||
end
|
||||
|
||||
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
|
||||
|
||||
result = ::WorkItems::CreateFromTaskService.new(
|
||||
work_item: work_item,
|
||||
current_user: current_user,
|
||||
work_item_params: work_item_data,
|
||||
spam_params: spam_params
|
||||
).execute
|
||||
|
||||
check_spam_action_response!(result[:work_item]) if result[:work_item]
|
||||
|
||||
response = { errors: result.errors }
|
||||
response.merge!(work_item: work_item, new_work_item: result[:work_item]) if result.success?
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_object(id:)
|
||||
# TODO: Remove coercion when working on https://gitlab.com/gitlab-org/gitlab/-/issues/257883
|
||||
id = ::Types::GlobalIDType[::WorkItem].coerce_isolated_input(id)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Graphql/GraphqlNamePosition
|
||||
module Types
|
||||
class BaseEnum < GraphQL::Schema::Enum
|
||||
class CustomValue < GraphQL::Schema::EnumValue
|
||||
|
|
|
@ -126,6 +126,7 @@ module Types
|
|||
mount_mutation Mutations::Packages::DestroyFile
|
||||
mount_mutation Mutations::Echo
|
||||
mount_mutation Mutations::WorkItems::Create
|
||||
mount_mutation Mutations::WorkItems::CreateFromTask
|
||||
mount_mutation Mutations::WorkItems::Delete
|
||||
mount_mutation Mutations::WorkItems::Update
|
||||
end
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module WorkItems
|
||||
class ConvertTaskInputType < BaseInputObject
|
||||
graphql_name 'WorkItemConvertTaskInput'
|
||||
|
||||
argument :line_number_end, GraphQL::Types::Int,
|
||||
required: true,
|
||||
description: 'Last line in the Markdown source that defines the list item task.'
|
||||
argument :line_number_start, GraphQL::Types::Int,
|
||||
required: true,
|
||||
description: 'First line in the Markdown source that defines the list item task.'
|
||||
argument :lock_version, GraphQL::Types::Int,
|
||||
required: true,
|
||||
description: 'Current lock version of the work item containing the task in the description.'
|
||||
argument :title, GraphQL::Types::String,
|
||||
required: true,
|
||||
description: 'Full string of the task to be replaced. New title for the created work item.'
|
||||
argument :work_item_type_id, ::Types::GlobalIDType[::WorkItems::Type],
|
||||
required: true,
|
||||
description: 'Global ID of the work item type used to create the new work item.',
|
||||
prepare: ->(attribute, _ctx) { work_item_type_global_id(attribute) }
|
||||
|
||||
class << self
|
||||
def work_item_type_global_id(global_id)
|
||||
# TODO: remove this line when the compatibility layer is removed
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
|
||||
global_id = ::Types::GlobalIDType[::WorkItems::Type].coerce_isolated_input(global_id)
|
||||
|
||||
global_id&.model_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
# Create and link operations are not run inside a transaction in this class
|
||||
# because CreateFromTaskService also creates a transaction.
|
||||
# This class should always be run inside a transaction as we could end up with
|
||||
# new work items that were never associated with other work items as expected.
|
||||
class CreateAndLinkService
|
||||
def initialize(project:, current_user: nil, params: {}, spam_params:, link_params: {})
|
||||
@create_service = CreateService.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: params,
|
||||
spam_params: spam_params
|
||||
)
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@link_params = link_params
|
||||
end
|
||||
|
||||
def execute
|
||||
create_result = @create_service.execute
|
||||
return create_result if create_result.error?
|
||||
|
||||
work_item = create_result[:work_item]
|
||||
return ::ServiceResponse.success(payload: payload(work_item)) if @link_params.blank?
|
||||
|
||||
result = IssueLinks::CreateService.new(work_item, @current_user, @link_params).execute
|
||||
|
||||
if result[:status] == :success
|
||||
::ServiceResponse.success(payload: payload(work_item))
|
||||
else
|
||||
::ServiceResponse.error(message: result[:message], http_status: 404)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def payload(work_item)
|
||||
{ work_item: work_item }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
class CreateFromTaskService
|
||||
def initialize(work_item:, current_user: nil, work_item_params: {}, spam_params:)
|
||||
@work_item = work_item
|
||||
@current_user = current_user
|
||||
@work_item_params = work_item_params
|
||||
@spam_params = spam_params
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def execute
|
||||
transaction_result = ApplicationRecord.transaction do
|
||||
create_and_link_result = CreateAndLinkService.new(
|
||||
project: @work_item.project,
|
||||
current_user: @current_user,
|
||||
params: @work_item_params.slice(:title, :work_item_type_id),
|
||||
spam_params: @spam_params,
|
||||
link_params: { target_issuable: @work_item }
|
||||
).execute
|
||||
|
||||
if create_and_link_result.error?
|
||||
@errors += create_and_link_result.errors
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
replacement_result = TaskListReferenceReplacementService.new(
|
||||
work_item: @work_item,
|
||||
work_item_reference: create_and_link_result[:work_item].to_reference,
|
||||
line_number_start: @work_item_params[:line_number_start],
|
||||
line_number_end: @work_item_params[:line_number_end],
|
||||
title: @work_item_params[:title],
|
||||
lock_version: @work_item_params[:lock_version]
|
||||
).execute
|
||||
|
||||
if replacement_result.error?
|
||||
@errors += replacement_result.errors
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
create_and_link_result
|
||||
end
|
||||
|
||||
return transaction_result if transaction_result
|
||||
|
||||
::ServiceResponse.error(message: @errors, http_status: 422)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
class TaskListReferenceReplacementService
|
||||
STALE_OBJECT_MESSAGE = 'Stale work item. Check lock version'
|
||||
|
||||
def initialize(work_item:, work_item_reference:, line_number_start:, line_number_end:, title:, lock_version:)
|
||||
@work_item = work_item
|
||||
@work_item_reference = work_item_reference
|
||||
@line_number_start = line_number_start
|
||||
@line_number_end = line_number_end
|
||||
@title = title
|
||||
@lock_version = lock_version
|
||||
end
|
||||
|
||||
def execute
|
||||
return ::ServiceResponse.error(message: STALE_OBJECT_MESSAGE) if @work_item.lock_version > @lock_version
|
||||
return ::ServiceResponse.error(message: 'line_number_start must be greater than 0') if @line_number_start < 1
|
||||
return ::ServiceResponse.error(message: 'line_number_end must be greater or equal to line_number_start') if @line_number_end < @line_number_start
|
||||
return ::ServiceResponse.error(message: "Work item description can't be blank") if @work_item.description.blank?
|
||||
|
||||
source_lines = @work_item.description.split("\n")
|
||||
markdown_task_first_line = source_lines[@line_number_start - 1]
|
||||
task_line = Taskable::ITEM_PATTERN.match(markdown_task_first_line)
|
||||
|
||||
return ::ServiceResponse.error(message: "Unable to detect a task on line #{@line_number_start}") unless task_line
|
||||
|
||||
captures = task_line.captures
|
||||
|
||||
markdown_task_first_line.sub!(Taskable::ITEM_PATTERN, "#{captures[0]} #{captures[1]} #{@work_item_reference}+")
|
||||
|
||||
source_lines[@line_number_start - 1] = markdown_task_first_line
|
||||
remove_additional_lines!(source_lines)
|
||||
|
||||
@work_item.update!(description: source_lines.join("\n"))
|
||||
|
||||
::ServiceResponse.success
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
::ServiceResponse.error(message: STALE_OBJECT_MESSAGE)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove_additional_lines!(source_lines)
|
||||
return if @line_number_end <= @line_number_start
|
||||
|
||||
source_lines.delete_if.each_with_index do |_line, index|
|
||||
index >= @line_number_start && index < @line_number_end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
- name: "GitLab self-monitoring" # The name of the feature to be deprecated
|
||||
announcement_milestone: "14.8" # The milestone when this feature was first announced as deprecated.
|
||||
announcement_date: "2022-02-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||
breaking_change: false # If this deprecation is a breaking change, set this value to true
|
||||
reporter: abellucci # GitLab username of the person reporting the deprecation
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
GitLab self-monitoring gives administrators of self-hosted GitLab instances the tools to monitor the health of their instances. This feature is deprecated in GitLab 14.8, but is not scheduled for removal. For more information, see our official [Statement of Support](https://about.gitlab.com/support/statement-of-support.html#gitlab-self-monitoring).
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
stage: Monitor # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
|
||||
tiers: [Core, Premium, Ultimate] # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348909 # (optional) This is a link to the deprecation issue in GitLab
|
||||
documentation_url: https://docs.gitlab.com/ee/administration/monitoring/gitlab_self_monitoring_project/ # (optional) This is a link to the current documentation page
|
|
@ -1704,3 +1704,38 @@ What does this mean? This strongly suggests that the S3 user does not have the r
|
|||
[permissions to perform a HEAD request](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html).
|
||||
The solution: check the [IAM permissions again](https://docs.docker.com/registry/storage-drivers/s3/).
|
||||
Once the right permissions were set, the error goes away.
|
||||
|
||||
### Missing `gitlab-registry.key` prevents container repository deletion
|
||||
|
||||
If you disable your GitLab instance's Container Registry and try to remove a project that has
|
||||
container repositories, the following error occurs:
|
||||
|
||||
```plaintext
|
||||
Errno::ENOENT: No such file or directory @ rb_sysopen - /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key
|
||||
```
|
||||
|
||||
In this case, follow these steps:
|
||||
|
||||
1. Temporarily enable the instance-wide setting for the Container Registry in your `gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['registry_enabled'] = true
|
||||
```
|
||||
|
||||
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure)
|
||||
for the changes to take effect.
|
||||
1. Try the removal again.
|
||||
|
||||
If you still can't remove the repository using the common methods, you can use the
|
||||
[GitLab Rails console](../troubleshooting/navigating_gitlab_via_rails_console.md)
|
||||
to remove the project by force:
|
||||
|
||||
```ruby
|
||||
# Path to the project you'd like to remove
|
||||
prj = Project.find_by_full_path(<project_path>)
|
||||
|
||||
# The following will delete the project's container registry, so be sure to double-check the path beforehand!
|
||||
if prj.has_container_registry_tags?
|
||||
prj.container_repositories.each { |p| p.destroy }
|
||||
end
|
||||
```
|
||||
|
|
|
@ -5178,6 +5178,29 @@ Input type: `WorkItemCreateInput`
|
|||
| <a id="mutationworkitemcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationworkitemcreateworkitem"></a>`workItem` | [`WorkItem`](#workitem) | Created work item. |
|
||||
|
||||
### `Mutation.workItemCreateFromTask`
|
||||
|
||||
Creates a work item from a task in another work item's description. Available only when feature flag `work_items` is enabled. This feature is experimental and is subject to change without notice.
|
||||
|
||||
Input type: `WorkItemCreateFromTaskInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationworkitemcreatefromtaskclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationworkitemcreatefromtaskid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
|
||||
| <a id="mutationworkitemcreatefromtaskworkitemdata"></a>`workItemData` | [`WorkItemConvertTaskInput!`](#workitemconverttaskinput) | Arguments necessary to convert a task into a work item. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationworkitemcreatefromtaskclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationworkitemcreatefromtaskerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationworkitemcreatefromtasknewworkitem"></a>`newWorkItem` | [`WorkItem`](#workitem) | New work item created from task. |
|
||||
| <a id="mutationworkitemcreatefromtaskworkitem"></a>`workItem` | [`WorkItem`](#workitem) | Updated work item. |
|
||||
|
||||
### `Mutation.workItemDelete`
|
||||
|
||||
Deletes a work item. Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice.
|
||||
|
@ -19901,3 +19924,15 @@ A time-frame defined as a closed inclusive range of two dates.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="vulnerabilityscannervendorinputname"></a>`name` | [`String!`](#string) | Name of the vendor/maintainer. |
|
||||
|
||||
### `WorkItemConvertTaskInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemconverttaskinputlinenumberend"></a>`lineNumberEnd` | [`Int!`](#int) | Last line in the Markdown source that defines the list item task. |
|
||||
| <a id="workitemconverttaskinputlinenumberstart"></a>`lineNumberStart` | [`Int!`](#int) | First line in the Markdown source that defines the list item task. |
|
||||
| <a id="workitemconverttaskinputlockversion"></a>`lockVersion` | [`Int!`](#int) | Current lock version of the work item containing the task in the description. |
|
||||
| <a id="workitemconverttaskinputtitle"></a>`title` | [`String!`](#string) | Full string of the task to be replaced. New title for the created work item. |
|
||||
| <a id="workitemconverttaskinputworkitemtypeid"></a>`workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of the work item type used to create the new work item. |
|
||||
|
|
|
@ -442,10 +442,10 @@ booleans:
|
|||
|
||||
```ruby
|
||||
class MergeRequestPermissionsType < BasePermissionType
|
||||
present_using MergeRequestPresenter
|
||||
|
||||
graphql_name 'MergeRequestPermissions'
|
||||
|
||||
present_using MergeRequestPresenter
|
||||
|
||||
abilities :admin_merge_request, :update_merge_request, :create_note
|
||||
|
||||
ability_field :resolve_note,
|
||||
|
@ -1329,6 +1329,10 @@ class UserUpdateMutation < BaseMutation
|
|||
end
|
||||
```
|
||||
|
||||
Due to changes in the `1.13` version of the `graphql-ruby` gem, `graphql_name` should be the first
|
||||
line of the class to ensure that type names are generated correctly. The `Graphql::GraphqlNamePosition` cop enforces this.
|
||||
See [issue #27536](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27536#note_840245581) for further context.
|
||||
|
||||
Our GraphQL mutation names are historically inconsistent, but new mutation names should follow the
|
||||
convention `'{Resource}{Action}'` or `'{Resource}{Action}{Attribute}'`.
|
||||
|
||||
|
@ -1511,9 +1515,9 @@ GraphQL-name of the mutation:
|
|||
```ruby
|
||||
module Types
|
||||
class MutationType < BaseObject
|
||||
include Gitlab::Graphql::MountMutation
|
||||
graphql_name 'Mutation'
|
||||
|
||||
graphql_name "Mutation"
|
||||
include Gitlab::Graphql::MountMutation
|
||||
|
||||
mount_mutation Mutations::MergeRequests::SetDraft
|
||||
end
|
||||
|
|
|
@ -41,8 +41,10 @@ least Maintainer [permissions](../user/permissions.md) to enable the Sentry inte
|
|||
|
||||
1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance.
|
||||
1. [Create](https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/) a new Sentry project. For each GitLab project that you want to integrate, we recommend that you create a new Sentry project.
|
||||
1. [Find or generate](https://docs.sentry.io/api/auth/) a Sentry auth token for your Sentry project.
|
||||
Make sure to give the token at least the following scopes: `event:read`, `project:read`, and `event:write` (for resolving events).
|
||||
1. Find or generate a [Sentry auth token](https://docs.sentry.io/api/auth/#auth-tokens).
|
||||
For the SaaS version of Sentry, you can find or generate the auth token at [https://sentry.io/api/](https://sentry.io/api/).
|
||||
Make sure to give the token at least the following scopes: `project:read`, `event:read`, and
|
||||
`event:write` (for resolving events).
|
||||
1. In GitLab, enable error tracking:
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Monitor > Error Tracking**.
|
||||
|
|
|
@ -900,6 +900,10 @@ To align with this change, API calls to list external status checks will also re
|
|||
|
||||
**Planned removal milestone: 15.0 (2022-05-22)**
|
||||
|
||||
### GitLab self-monitoring
|
||||
|
||||
GitLab self-monitoring gives administrators of self-hosted GitLab instances the tools to monitor the health of their instances. This feature is deprecated in GitLab 14.8, but is not scheduled for removal. For more information, see our official [Statement of Support](https://about.gitlab.com/support/statement-of-support.html#gitlab-self-monitoring).
|
||||
|
||||
### GraphQL ID and GlobalID compatibility
|
||||
|
||||
WARNING:
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This module has the necessary methods to render
|
||||
# work items hierarchy menu
|
||||
module Sidebars
|
||||
module Concerns
|
||||
module WorkItemHierarchy
|
||||
def hierarchy_menu_item(container, url, path)
|
||||
unless show_hierarachy_menu_item?(container)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :hierarchy)
|
||||
end
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Planning hierarchy'),
|
||||
link: url,
|
||||
active_routes: { path: path },
|
||||
item_id: :hierarchy
|
||||
)
|
||||
end
|
||||
|
||||
def show_hierarachy_menu_item?(container)
|
||||
can?(context.current_user, :read_planning_hierarchy, container)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,13 +4,10 @@ module Sidebars
|
|||
module Projects
|
||||
module Menus
|
||||
class ProjectInformationMenu < ::Sidebars::Menu
|
||||
include ::Sidebars::Concerns::WorkItemHierarchy
|
||||
|
||||
override :configure_menu_items
|
||||
def configure_menu_items
|
||||
add_item(activity_menu_item)
|
||||
add_item(labels_menu_item)
|
||||
add_item(hierarchy_menu_item(context.project, project_planning_hierarchy_path(context.project), 'projects#planning_hierarchy'))
|
||||
add_item(members_menu_item)
|
||||
|
||||
true
|
||||
|
|
|
@ -49,10 +49,63 @@ module QA
|
|||
end
|
||||
|
||||
context "when tls is disabled" do
|
||||
where(:authentication_token_type, :token_name) do
|
||||
:personal_access_token | 'Personal Access Token'
|
||||
:project_deploy_token | 'Deploy Token'
|
||||
:ci_job_token | 'Job Token'
|
||||
where do
|
||||
{
|
||||
'using docker:18.09.9 and a personal access token' => {
|
||||
docker_client_version: 'docker:18.09.9',
|
||||
authentication_token_type: :personal_access_token,
|
||||
token_name: 'Personal Access Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348499'
|
||||
},
|
||||
'using docker:18.09.9 and a project deploy token' => {
|
||||
docker_client_version: 'docker:18.09.9',
|
||||
authentication_token_type: :project_deploy_token,
|
||||
token_name: 'Deploy Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348852'
|
||||
},
|
||||
'using docker:18.09.9 and a ci job token' => {
|
||||
docker_client_version: 'docker:18.09.9',
|
||||
authentication_token_type: :ci_job_token,
|
||||
token_name: 'Job Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348765'
|
||||
},
|
||||
'using docker:19.03.12 and a personal access token' => {
|
||||
docker_client_version: 'docker:19.03.12',
|
||||
authentication_token_type: :personal_access_token,
|
||||
token_name: 'Personal Access Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348507'
|
||||
},
|
||||
'using docker:19.03.12 and a project deploy token' => {
|
||||
docker_client_version: 'docker:19.03.12',
|
||||
authentication_token_type: :project_deploy_token,
|
||||
token_name: 'Deploy Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348859'
|
||||
},
|
||||
'using docker:19.03.12 and a ci job token' => {
|
||||
docker_client_version: 'docker:19.03.12',
|
||||
authentication_token_type: :ci_job_token,
|
||||
token_name: 'Job Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348654'
|
||||
},
|
||||
'using docker:20.10 and a personal access token' => {
|
||||
docker_client_version: 'docker:20.10',
|
||||
authentication_token_type: :personal_access_token,
|
||||
token_name: 'Personal Access Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348754'
|
||||
},
|
||||
'using docker:20.10 and a project deploy token' => {
|
||||
docker_client_version: 'docker:20.10',
|
||||
authentication_token_type: :project_deploy_token,
|
||||
token_name: 'Deploy Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348856'
|
||||
},
|
||||
'using docker:20.10 and a ci job token' => {
|
||||
docker_client_version: 'docker:20.10',
|
||||
authentication_token_type: :ci_job_token,
|
||||
token_name: 'Job Token',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348766'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -78,57 +131,51 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
where(:docker_client_version) do
|
||||
%w[docker:18.09.9 docker:19.03.12 docker:20.10]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "pushes image and deletes tag", :registry do
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
commit.project = project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
commit.add_files([{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
content:
|
||||
<<~YAML
|
||||
build:
|
||||
image: "#{docker_client_version}"
|
||||
stage: build
|
||||
services:
|
||||
- name: "#{docker_client_version}-dind"
|
||||
command: ["--insecure-registry=gitlab.test:5050"]
|
||||
variables:
|
||||
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
script:
|
||||
- docker login -u #{auth_user} -p #{auth_token} gitlab.test:5050
|
||||
- docker build -t $IMAGE_TAG .
|
||||
- docker push $IMAGE_TAG
|
||||
tags:
|
||||
- "runner-for-#{project.name}"
|
||||
YAML
|
||||
}])
|
||||
end
|
||||
it "pushes image and deletes tag", :registry, testcase: params[:testcase] do
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
commit.project = project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
commit.add_files([{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
content:
|
||||
<<~YAML
|
||||
build:
|
||||
image: "#{docker_client_version}"
|
||||
stage: build
|
||||
services:
|
||||
- name: "#{docker_client_version}-dind"
|
||||
command: ["--insecure-registry=gitlab.test:5050"]
|
||||
variables:
|
||||
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
script:
|
||||
- docker login -u #{auth_user} -p #{auth_token} gitlab.test:5050
|
||||
- docker build -t $IMAGE_TAG .
|
||||
- docker push $IMAGE_TAG
|
||||
tags:
|
||||
- "runner-for-#{project.name}"
|
||||
YAML
|
||||
}])
|
||||
end
|
||||
end
|
||||
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('build')
|
||||
end
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('build')
|
||||
end
|
||||
|
||||
Page::Project::Job::Show.perform do |job|
|
||||
expect(job).to be_successful(timeout: 800)
|
||||
end
|
||||
Page::Project::Job::Show.perform do |job|
|
||||
expect(job).to be_successful(timeout: 800)
|
||||
end
|
||||
|
||||
Page::Project::Menu.perform(&:go_to_container_registry)
|
||||
Page::Project::Menu.perform(&:go_to_container_registry)
|
||||
|
||||
Page::Project::Registry::Show.perform do |registry|
|
||||
expect(registry).to have_registry_repository(project.path_with_namespace)
|
||||
Page::Project::Registry::Show.perform do |registry|
|
||||
expect(registry).to have_registry_repository(project.path_with_namespace)
|
||||
|
||||
registry.click_on_image(project.path_with_namespace)
|
||||
expect(registry).to have_tag('master')
|
||||
end
|
||||
registry.click_on_image(project.path_with_namespace)
|
||||
expect(registry).to have_tag('master')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -156,7 +203,7 @@ module QA
|
|||
apk add --no-cache openssl
|
||||
true | openssl s_client -showcerts -connect gitlab.test:5050 > /usr/local/share/ca-certificates/gitlab.test.crt
|
||||
update-ca-certificates
|
||||
dockerd-entrypoint.sh || exit
|
||||
dockerd-entrypoint.sh || exit
|
||||
variables:
|
||||
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
script:
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This cop ensures that if a class uses `graphql_name`, then
|
||||
# it's the first line of the class
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# class AwfulClass
|
||||
# field :some_field, GraphQL::Types::JSON
|
||||
# graphql_name 'AwfulClass'
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# class GreatClass
|
||||
# graphql_name 'AwfulClass'
|
||||
# field :some_field, GraphQL::Types::String
|
||||
# end
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Graphql
|
||||
class GraphqlNamePosition < RuboCop::Cop::Cop
|
||||
MSG = '`graphql_name` should be the first line of the class: '\
|
||||
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#naming-conventions'
|
||||
|
||||
def_node_search :graphql_name?, <<~PATTERN
|
||||
(send nil? :graphql_name ...)
|
||||
PATTERN
|
||||
|
||||
def on_class(node)
|
||||
return unless graphql_name?(node)
|
||||
return if node.body.single_line?
|
||||
|
||||
add_offense(node, location: :expression) unless graphql_name?(node.body.children.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,7 +35,7 @@ describe('Contributors Store Getters', () => {
|
|||
{ author_name: 'Carlson', author_email: 'carlson123@gmail.com', date: '2019-05-05' },
|
||||
{ author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-04-04' },
|
||||
{ author_name: 'Johan', author_email: 'jawnnypoo@gmail.com', date: '2019-04-04' },
|
||||
{ author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-03-03' },
|
||||
{ author_name: 'John', author_email: 'JAWNNYPOO@gmail.com', date: '2019-03-03' },
|
||||
];
|
||||
parsed = getters.parsedData(state);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['WorkItem'] do
|
||||
specify { expect(described_class.graphql_name).to eq('WorkItem') }
|
||||
|
||||
it 'has specific fields' do
|
||||
fields = %i[description description_html id iid state title title_html work_item_type]
|
||||
|
||||
fields.each do |field_name|
|
||||
expect(described_class).to have_graphql_fields(*fields)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Concerns::WorkItemHierarchy do
|
||||
shared_examples 'hierarchy menu' do
|
||||
let(:item_id) { :hierarchy }
|
||||
specify { is_expected.not_to be_nil }
|
||||
end
|
||||
|
||||
describe 'Project hierarchy menu item' do
|
||||
let_it_be_with_reload(:project) { create(:project, :repository) }
|
||||
|
||||
let(:user) { project.owner }
|
||||
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
|
||||
|
||||
subject { Sidebars::Projects::Menus::ProjectInformationMenu.new(context).renderable_items.index { |e| e.item_id == item_id } }
|
||||
|
||||
it_behaves_like 'hierarchy menu'
|
||||
end
|
||||
end
|
|
@ -59,11 +59,5 @@ RSpec.describe Sidebars::Projects::Menus::ProjectInformationMenu do
|
|||
specify { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Hierarchy' do
|
||||
let(:item_id) { :hierarchy }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe "Create a work item from a task in a work item's description" do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
|
||||
let_it_be(:work_item, refind: true) { create(:work_item, project: project, description: '- [ ] A task in a list', lock_version: 3) }
|
||||
|
||||
let(:lock_version) { work_item.lock_version }
|
||||
let(:input) do
|
||||
{
|
||||
'id' => work_item.to_global_id.to_s,
|
||||
'workItemData' => {
|
||||
'title' => 'A task in a list',
|
||||
'workItemTypeId' => WorkItems::Type.default_by_type(:task).to_global_id.to_s,
|
||||
'lineNumberStart' => 1,
|
||||
'lineNumberEnd' => 1,
|
||||
'lockVersion' => lock_version
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:mutation) { graphql_mutation(:workItemCreateFromTask, input) }
|
||||
let(:mutation_response) { graphql_mutation_response(:work_item_create_from_task) }
|
||||
|
||||
context 'the user is not allowed to update a work item' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it_behaves_like 'a mutation that returns a top-level access error'
|
||||
end
|
||||
|
||||
context 'when user has permissions to create a work item' do
|
||||
let(:current_user) { developer }
|
||||
|
||||
it 'creates the work item' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change(WorkItem, :count).by(1)
|
||||
|
||||
created_work_item = WorkItem.last
|
||||
work_item.reload
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(work_item.description).to eq("- [ ] #{created_work_item.to_reference}+")
|
||||
expect(created_work_item.issue_type).to eq('task')
|
||||
expect(created_work_item.work_item_type.base_type).to eq('task')
|
||||
expect(mutation_response['workItem']).to include('id' => work_item.to_global_id.to_s)
|
||||
expect(mutation_response['newWorkItem']).to include('id' => created_work_item.to_global_id.to_s)
|
||||
end
|
||||
|
||||
context 'when creating a work item fails' do
|
||||
let(:lock_version) { 2 }
|
||||
|
||||
it 'makes no changes to the DB and returns an error message' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
work_item.reload
|
||||
end.to not_change(WorkItem, :count).and(
|
||||
not_change(work_item, :description)
|
||||
)
|
||||
|
||||
expect(mutation_response['errors']).to contain_exactly('Stale work item. Check lock version')
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'has spam protection' do
|
||||
let(:mutation_class) { ::Mutations::WorkItems::CreateFromTask }
|
||||
end
|
||||
|
||||
context 'when the work_items feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(work_items: false)
|
||||
end
|
||||
|
||||
it 'does nothing and returns and error' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to not_change(WorkItem, :count)
|
||||
|
||||
expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
require_relative '../../../../rubocop/cop/graphql/graphql_name_position'
|
||||
|
||||
RSpec.describe RuboCop::Cop::Graphql::GraphqlNamePosition do
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
it 'adds an offense when graphql_name is not on the first line' do
|
||||
expect_offense(<<~TYPE)
|
||||
module Types
|
||||
class AType < BaseObject
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^ `graphql_name` should be the first line of the class: https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#naming-conventions
|
||||
field :a_thing
|
||||
field :another_thing
|
||||
graphql_name 'ATypeName'
|
||||
end
|
||||
end
|
||||
TYPE
|
||||
end
|
||||
|
||||
it 'does not add an offense for classes that have no call to graphql_name' do
|
||||
expect_no_offenses(<<~TYPE.strip)
|
||||
module Types
|
||||
class AType < BaseObject
|
||||
authorize :an_ability, :second_ability
|
||||
|
||||
field :a_thing
|
||||
end
|
||||
end
|
||||
TYPE
|
||||
end
|
||||
|
||||
it 'does not add an offense for classes that only call graphql_name' do
|
||||
expect_no_offenses(<<~TYPE.strip)
|
||||
module Types
|
||||
class AType < BaseObject
|
||||
graphql_name 'ATypeName'
|
||||
end
|
||||
end
|
||||
TYPE
|
||||
end
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::CreateAndLinkService do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:related_work_item) { create(:work_item, project: project) }
|
||||
|
||||
let(:spam_params) { double }
|
||||
let(:link_params) { {} }
|
||||
let(:params) do
|
||||
{
|
||||
title: 'Awesome work item',
|
||||
description: 'please fix'
|
||||
}
|
||||
end
|
||||
|
||||
before_all do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:service_result) { described_class.new(project: project, current_user: user, params: params, spam_params: spam_params, link_params: link_params).execute }
|
||||
|
||||
before do
|
||||
stub_spam_services
|
||||
end
|
||||
|
||||
context 'when work item params are valid' do
|
||||
it { is_expected.to be_success }
|
||||
|
||||
it 'creates a work item successfully with no links' do
|
||||
expect do
|
||||
service_result
|
||||
end.to change(WorkItem, :count).by(1).and(
|
||||
not_change(IssueLink, :count)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when link params are valid' do
|
||||
let(:link_params) { { issuable_references: [related_work_item.to_reference] } }
|
||||
|
||||
it 'creates a work item successfully with links' do
|
||||
expect do
|
||||
service_result
|
||||
end.to change(WorkItem, :count).by(1).and(
|
||||
change(IssueLink, :count).by(1)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when link params are invalid' do
|
||||
let(:link_params) { { issuable_references: ['invalid reference'] } }
|
||||
|
||||
it { is_expected.to be_error }
|
||||
|
||||
it 'does not create a link and does not rollback transaction' do
|
||||
expect do
|
||||
service_result
|
||||
end.to not_change(IssueLink, :count).and(
|
||||
change(WorkItem, :count).by(1)
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns a link creation error message' do
|
||||
expect(service_result.errors).to contain_exactly('No matching issue found. Make sure that you are adding a valid issue URL.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when work item params are invalid' do
|
||||
let(:params) do
|
||||
{
|
||||
title: '',
|
||||
description: 'invalid work item'
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to be_error }
|
||||
|
||||
it 'does not create a work item or links' do
|
||||
expect do
|
||||
service_result
|
||||
end.to not_change(WorkItem, :count).and(
|
||||
not_change(IssueLink, :count)
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns work item errors' do
|
||||
expect(service_result.errors).to contain_exactly("Title can't be blank")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,97 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::CreateFromTaskService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:list_work_item, refind: true) { create(:work_item, project: project, description: "- [ ] Item to be converted\n second line\n third line") }
|
||||
|
||||
let(:work_item_to_update) { list_work_item }
|
||||
let(:spam_params) { double }
|
||||
let(:link_params) { {} }
|
||||
let(:current_user) { developer }
|
||||
let(:params) do
|
||||
{
|
||||
title: 'Awesome work item',
|
||||
work_item_type_id: WorkItems::Type.default_by_type(:task).id,
|
||||
line_number_start: 1,
|
||||
line_number_end: 3,
|
||||
lock_version: work_item_to_update.lock_version
|
||||
}
|
||||
end
|
||||
|
||||
before_all do
|
||||
project.add_developer(developer)
|
||||
end
|
||||
|
||||
shared_examples 'CreateFromTask service with invalid params' do
|
||||
it { is_expected.to be_error }
|
||||
|
||||
it 'does not create a work item or links' do
|
||||
expect do
|
||||
service_result
|
||||
end.to not_change(WorkItem, :count).and(
|
||||
not_change(IssueLink, :count)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:service_result) { described_class.new(work_item: work_item_to_update, current_user: current_user, work_item_params: params, spam_params: spam_params).execute }
|
||||
|
||||
before do
|
||||
stub_spam_services
|
||||
end
|
||||
|
||||
context 'when work item params are valid' do
|
||||
it { is_expected.to be_success }
|
||||
|
||||
it 'creates a work item and links it to the original work item successfully' do
|
||||
expect do
|
||||
service_result
|
||||
end.to change(WorkItem, :count).by(1).and(
|
||||
change(IssueLink, :count)
|
||||
)
|
||||
end
|
||||
|
||||
it 'replaces the original issue markdown description with new work item reference' do
|
||||
service_result
|
||||
|
||||
created_work_item = WorkItem.last
|
||||
|
||||
expect(list_work_item.description).to eq("- [ ] #{created_work_item.to_reference}+")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last operation fails' do
|
||||
before do
|
||||
params.merge!(line_number_start: 0)
|
||||
end
|
||||
|
||||
it 'rollbacks all operations' do
|
||||
expect do
|
||||
service_result
|
||||
end.to not_change(WorkItem, :count).and(
|
||||
not_change(IssueLink, :count)
|
||||
)
|
||||
end
|
||||
|
||||
it { is_expected.to be_error }
|
||||
|
||||
it 'returns an error message' do
|
||||
expect(service_result.errors).to contain_exactly('line_number_start must be greater than 0')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when work item params are invalid' do
|
||||
let(:params) { { title: '' } }
|
||||
|
||||
it_behaves_like 'CreateFromTask service with invalid params'
|
||||
|
||||
it 'returns work item errors' do
|
||||
expect(service_result.errors).to contain_exactly("Title can't be blank")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,106 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::TaskListReferenceReplacementService do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:single_line_work_item, refind: true) { create(:work_item, project: project, description: '- [ ] single line', lock_version: 3) }
|
||||
let_it_be(:multiple_line_work_item, refind: true) { create(:work_item, project: project, description: "Any text\n\n* [ ] Item to be converted\n second line\n third line", lock_version: 3) }
|
||||
|
||||
let(:line_number_start) { 3 }
|
||||
let(:line_number_end) { 5 }
|
||||
let(:title) { 'work item title' }
|
||||
let(:reference) { 'any reference' }
|
||||
let(:work_item) { multiple_line_work_item }
|
||||
let(:lock_version) { 3 }
|
||||
let(:expected_additional_text) { '' }
|
||||
|
||||
shared_examples 'successful work item task reference replacement service' do
|
||||
it { is_expected.to be_success }
|
||||
|
||||
it 'replaces the original issue markdown description with new work item reference' do
|
||||
result
|
||||
|
||||
expect(work_item.description).to eq("#{expected_additional_text}#{task_prefix} #{reference}+")
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'failing work item task reference replacement service' do |error_message|
|
||||
it { is_expected.to be_error }
|
||||
|
||||
it 'returns an error message' do
|
||||
expect(result.errors).to contain_exactly(error_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:result) do
|
||||
described_class.new(
|
||||
work_item: work_item,
|
||||
work_item_reference: reference,
|
||||
line_number_start: line_number_start,
|
||||
line_number_end: line_number_end,
|
||||
title: title,
|
||||
lock_version: lock_version
|
||||
).execute
|
||||
end
|
||||
|
||||
context 'when task mardown spans a single line' do
|
||||
let(:line_number_start) { 1 }
|
||||
let(:line_number_end) { 1 }
|
||||
let(:work_item) { single_line_work_item }
|
||||
let(:task_prefix) { '- [ ]' }
|
||||
|
||||
it_behaves_like 'successful work item task reference replacement service'
|
||||
end
|
||||
|
||||
context 'when task mardown spans multiple lines' do
|
||||
let(:task_prefix) { '* [ ]' }
|
||||
let(:expected_additional_text) { "Any text\n\n" }
|
||||
|
||||
it_behaves_like 'successful work item task reference replacement service'
|
||||
end
|
||||
|
||||
context 'when description does not contain a task' do
|
||||
let_it_be(:no_matching_work_item) { create(:work_item, project: project, description: 'no matching task') }
|
||||
|
||||
let(:work_item) { no_matching_work_item }
|
||||
|
||||
it_behaves_like 'failing work item task reference replacement service', 'Unable to detect a task on line 3'
|
||||
end
|
||||
|
||||
context 'when description is empty' do
|
||||
let_it_be(:empty_work_item) { create(:work_item, project: project, description: '') }
|
||||
|
||||
let(:work_item) { empty_work_item }
|
||||
|
||||
it_behaves_like 'failing work item task reference replacement service', "Work item description can't be blank"
|
||||
end
|
||||
|
||||
context 'when line_number_start is lower than 1' do
|
||||
let(:line_number_start) { 0 }
|
||||
|
||||
it_behaves_like 'failing work item task reference replacement service', 'line_number_start must be greater than 0'
|
||||
end
|
||||
|
||||
context 'when line_number_end is lower than line_number_start' do
|
||||
let(:line_number_end) { line_number_start - 1 }
|
||||
|
||||
it_behaves_like 'failing work item task reference replacement service', 'line_number_end must be greater or equal to line_number_start'
|
||||
end
|
||||
|
||||
context 'when lock_version is older than current' do
|
||||
let(:lock_version) { 2 }
|
||||
|
||||
it_behaves_like 'failing work item task reference replacement service', 'Stale work item. Check lock version'
|
||||
end
|
||||
|
||||
context 'when work item is stale before updating' do
|
||||
it_behaves_like 'failing work item task reference replacement service', 'Stale work item. Check lock version' do
|
||||
before do
|
||||
::WorkItem.where(id: work_item.id).update_all(lock_version: lock_version + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,7 +22,6 @@ RSpec.shared_context 'project navbar structure' do
|
|||
nav_sub_items: [
|
||||
_('Activity'),
|
||||
_('Labels'),
|
||||
_('Planning hierarchy'),
|
||||
_('Members')
|
||||
]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue