Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-09-05 18:09:06 +00:00
parent 27d47e40e1
commit 184b3d4a21
26 changed files with 204 additions and 77 deletions

View File

@ -1 +1 @@
v16.4.0-rc1
v16.4.0-rc2

View File

@ -41,7 +41,7 @@ gem 'sprockets', '~> 3.7.0'
gem 'view_component', '~> 3.5.0'
# Supported DBs
gem 'pg', '~> 1.5.3'
gem 'pg', '~> 1.5.4'
gem 'neighbor', '~> 0.2.3'

View File

@ -443,10 +443,7 @@
{"name":"parslet","version":"1.8.2","platform":"ruby","checksum":"08d1ab3721cd3f175bfbee8788b2ddff71f92038f2d69bd65454c22bb9fbd98a"},
{"name":"pastel","version":"0.8.0","platform":"ruby","checksum":"481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75"},
{"name":"peek","version":"1.1.0","platform":"ruby","checksum":"d6501ead8cde46d8d8ed0d59eb6f0ba713d0a41c11a2c4a81447b2dce37b3ecc"},
{"name":"pg","version":"1.5.3","platform":"ruby","checksum":"6b9ee5e2d5aee975588232c41f8203e766157cf71dba54ee85b343a45ced9bfd"},
{"name":"pg","version":"1.5.3","platform":"x64-mingw-ucrt","checksum":"1f2a6b2afaf0ccb8afe8b6a00131bce8151fbd6e8826b2d944288f6f2b615389"},
{"name":"pg","version":"1.5.3","platform":"x64-mingw32","checksum":"ab7f5f3020323094a2b16f9638166b04c103e152a9079a1b8e795f4bf79765e0"},
{"name":"pg","version":"1.5.3","platform":"x86-mingw32","checksum":"aa6ddda9887462d30a6d49d875eb9d27fca8cdb7185103b650e7351b38f15ddf"},
{"name":"pg","version":"1.5.4","platform":"ruby","checksum":"04f7b247151c639a0b955d8e5a9a41541343f4640aa3c2bdf749a872c339d25d"},
{"name":"pg_query","version":"4.2.3","platform":"ruby","checksum":"1cc9955c7bce8e51e1abc11f1952e3d9d0f1cd4c16c58c56ec75d5aaf1cfd697"},
{"name":"plist","version":"3.6.0","platform":"ruby","checksum":"f468bcf6b72ec6d1585ed6744eb4817c1932a5bf91895ed056e69b7f12ca10f2"},
{"name":"png_quantizator","version":"0.2.1","platform":"ruby","checksum":"6023d4d064125c3a7e02929c95b7320ed6ac0d7341f9e8de0c9ea6576ef3106b"},

View File

@ -1190,7 +1190,7 @@ GEM
tty-color (~> 0.5)
peek (1.1.0)
railties (>= 4.0.0)
pg (1.5.3)
pg (1.5.4)
pg_query (4.2.3)
google-protobuf (>= 3.22.3)
plist (3.6.0)
@ -1942,7 +1942,7 @@ DEPENDENCIES
parser (~> 3.2, >= 3.2.2.3)
parslet (~> 1.8)
peek (~> 1.1)
pg (~> 1.5.3)
pg (~> 1.5.4)
pg_query (~> 4.2.3)
png_quantizator (~> 0.2.1)
premailer-rails (~> 1.10.3)

View File

@ -43,15 +43,6 @@ export default {
type: Boolean,
required: true,
},
parentWorkItemId: {
type: String,
required: true,
},
workItemType: {
type: String,
required: false,
default: '',
},
childPath: {
type: String,
required: true,

View File

@ -31,6 +31,7 @@ import {
WORK_ITEM_TYPE_VALUE_ISSUE,
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
WIDGET_TYPE_NOTES,
WIDGET_TYPE_LINKED_ITEMS,
} from '../constants';
import workItemUpdatedSubscription from '../graphql/work_item_updated.subscription.graphql';
@ -50,6 +51,7 @@ import WorkItemNotes from './work_item_notes.vue';
import WorkItemDetailModal from './work_item_detail_modal.vue';
import WorkItemAwardEmoji from './work_item_award_emoji.vue';
import WorkItemStateToggleButton from './work_item_state_toggle_button.vue';
import WorkItemRelationships from './work_item_relationships/work_item_relationships.vue';
export default {
i18n,
@ -79,6 +81,7 @@ export default {
AbuseCategorySelector,
GlIntersectionObserver,
ConfidentialityBadge,
WorkItemRelationships,
},
mixins: [glFeatureFlagMixin()],
inject: ['fullPath', 'reportAbusePath'],
@ -259,6 +262,15 @@ export default {
showIntersectionObserver() {
return !this.isModal && this.workItemsMvc2Enabled;
},
hasLinkedWorkItems() {
return this.glFeatures.linkedWorkItems;
},
workItemLinkedItems() {
return this.isWidgetPresent(WIDGET_TYPE_LINKED_ITEMS);
},
showWorkItemLinkedItems() {
return this.hasLinkedWorkItems && this.workItemLinkedItems;
},
},
mounted() {
if (this.modalWorkItemIid) {
@ -591,6 +603,11 @@ export default {
@show-modal="openInModal"
@addChild="$emit('addChild')"
/>
<work-item-relationships
v-if="showWorkItemLinkedItems"
:work-item-iid="workItemIid"
:work-item-fullpath="workItem.project.fullPath"
/>
<work-item-notes
v-if="workItemNotes"
:work-item-id="workItem.id"

View File

@ -13,6 +13,7 @@ import {
WIDGET_TYPE_HIERARCHY,
WORK_ITEM_NAME_TO_ICON_MAP,
} from '../../constants';
import { workItemPath } from '../../utils';
import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
import WorkItemLinkChildContents from '../shared/work_item_link_child_contents.vue';
import WorkItemTreeChildren from './work_item_tree_children.vue';
@ -90,7 +91,7 @@ export default {
return this.isItemOpen ? __('Created') : __('Closed');
},
childPath() {
return `${gon?.relative_url_root || ''}/${this.fullPath}/-/work_items/${this.childItem.iid}`;
return workItemPath(this.fullPath, this.childItem.iid);
},
chevronType() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';

View File

@ -0,0 +1,50 @@
<script>
import { GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
import WidgetWrapper from '../widget_wrapper.vue';
export default {
components: {
WidgetWrapper,
GlButton,
},
props: {
workItemIid: {
type: String,
required: true,
},
workItemFullpath: {
type: String,
required: true,
},
},
i18n: {
title: s__('WorkItem|Linked Items'),
emptyStateMessage: s__(
"WorkItem|Link work items together to show that they're related or that one is blocking others.",
),
addLinkedWorkItemButtonLabel: s__('WorkItem|Add'),
},
};
</script>
<template>
<widget-wrapper class="work-item-relationships">
<template #header>{{ $options.i18n.title }}</template>
<template #header-right>
<gl-button size="small" class="gl-ml-3">
<slot name="add-button-text">{{ $options.i18n.addLinkedWorkItemButtonLabel }}</slot>
</gl-button>
</template>
<template #body>
<div class="gl-new-card-content">
<div data-testid="links-empty">
<p class="gl-new-card-empty">
{{ $options.i18n.emptyStateMessage }}
</p>
</div>
</div>
</template>
</widget-wrapper>
</template>

View File

@ -26,6 +26,7 @@ export const WIDGET_TYPE_MILESTONE = 'MILESTONE';
export const WIDGET_TYPE_ITERATION = 'ITERATION';
export const WIDGET_TYPE_NOTES = 'NOTES';
export const WIDGET_TYPE_HEALTH_STATUS = 'HEALTH_STATUS';
export const WIDGET_TYPE_LINKED_ITEMS = 'LINKED_ITEMS';
export const WORK_ITEM_TYPE_ENUM_INCIDENT = 'INCIDENT';
export const WORK_ITEM_TYPE_ENUM_ISSUE = 'ISSUE';

View File

@ -100,4 +100,8 @@ fragment WorkItemWidgets on WorkItemWidget {
... on WorkItemWidgetAwardEmoji {
type
}
... on WorkItemWidgetLinkedItems {
type
}
}

View File

@ -1,3 +1,4 @@
import { joinPaths } from '~/lib/utils/url_utility';
import {
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_HEALTH_STATUS,
@ -42,3 +43,7 @@ export const markdownPreviewPath = (fullPath, iid) =>
`${
gon.relative_url_root || ''
}/${fullPath}/preview_markdown?target_type=WorkItem&target_id=${iid}`;
export const workItemPath = (fullPath, workItemIid) => {
return joinPaths(gon?.relative_url_root || '/', fullPath, '-', 'work_items', workItemIid);
};

View File

@ -12,6 +12,7 @@ class Projects::IncidentsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:move_close_into_dropdown, project)
push_force_frontend_feature_flag(:linked_work_items, @project&.linked_work_items_feature_flag_enabled?)
end
feature_category :incident_management

View File

@ -72,6 +72,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:move_close_into_dropdown, project)
push_force_frontend_feature_flag(:linked_work_items, project.linked_work_items_feature_flag_enabled?)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]

View File

@ -12,6 +12,7 @@ class Projects::WorkItemsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_mvc, project&.work_items_mvc_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_force_frontend_feature_flag(:saved_replies, current_user)
push_force_frontend_feature_flag(:linked_work_items, project&.linked_work_items_feature_flag_enabled?)
end
feature_category :team_planning

View File

@ -46,6 +46,7 @@ class ProjectsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc, @project&.work_items_mvc_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?)
push_force_frontend_feature_flag(:linked_work_items, @project&.linked_work_items_feature_flag_enabled?)
end
layout :determine_layout

View File

@ -130,10 +130,14 @@ module MergeRequests
if source_branch_default? && !target_branch_specified?
merge_request.target_branch = nil
else
merge_request.target_branch ||= target_project.default_branch
merge_request.target_branch ||= get_target_branch
end
end
def get_target_branch
target_project.default_branch
end
def source_branch_specified?
params[:source_branch].present?
end

View File

@ -5,4 +5,5 @@
= form_tag path do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/
= submit_tag _('Revoke'), class: 'gl-button btn btn-danger btn-sm', aria: { label: s_('AuthorizedApplication|Revoke application') }, data: { confirm: s_('AuthorizedApplication|Are you sure you want to revoke this application?'), confirm_btn_variant: 'danger' }
= render Pajamas::ButtonComponent.new(type: :submit, variant: :danger, size: :small, button_options: { aria: { label: s_('AuthorizedApplication|Revoke application') }, data: { confirm: s_('AuthorizedApplication|Are you sure you want to revoke this application?'), confirm_btn_variant: 'danger' } }) do
= _('Revoke')

View File

@ -74,13 +74,13 @@ four standard [pagination arguments](#connection-pagination-arguments):
### `Query.aiMessages`
Find AI messages.
Find AI Duo Chat messages.
WARNING:
**Introduced** in 16.1.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`AiCachedMessageTypeConnection!`](#aicachedmessagetypeconnection).
Returns [`AiChatMessageConnection!`](#aichatmessageconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
@ -91,7 +91,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryaimessagesrequestids"></a>`requestIds` | [`[ID!]`](#id) | Array of request IDs to fetch. |
| <a id="queryaimessagesroles"></a>`roles` | [`[AiCachedMessageRole!]`](#aicachedmessagerole) | Array of roles to fetch. |
| <a id="queryaimessagesroles"></a>`roles` | [`[AiChatMessageRole!]`](#aichatmessagerole) | Array of roles to fetch. |
### `Query.auditEventDefinitions`
@ -7775,28 +7775,28 @@ The edge type for [`AgentConfiguration`](#agentconfiguration).
| <a id="agentconfigurationedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="agentconfigurationedgenode"></a>`node` | [`AgentConfiguration`](#agentconfiguration) | The item at the end of the edge. |
#### `AiCachedMessageTypeConnection`
#### `AiChatMessageConnection`
The connection type for [`AiCachedMessageType`](#aicachedmessagetype).
The connection type for [`AiChatMessage`](#aichatmessage).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aicachedmessagetypeconnectionedges"></a>`edges` | [`[AiCachedMessageTypeEdge]`](#aicachedmessagetypeedge) | A list of edges. |
| <a id="aicachedmessagetypeconnectionnodes"></a>`nodes` | [`[AiCachedMessageType]`](#aicachedmessagetype) | A list of nodes. |
| <a id="aicachedmessagetypeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
| <a id="aichatmessageconnectionedges"></a>`edges` | [`[AiChatMessageEdge]`](#aichatmessageedge) | A list of edges. |
| <a id="aichatmessageconnectionnodes"></a>`nodes` | [`[AiChatMessage]`](#aichatmessage) | A list of nodes. |
| <a id="aichatmessageconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `AiCachedMessageTypeEdge`
#### `AiChatMessageEdge`
The edge type for [`AiCachedMessageType`](#aicachedmessagetype).
The edge type for [`AiChatMessage`](#aichatmessage).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aicachedmessagetypeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="aicachedmessagetypeedgenode"></a>`node` | [`AiCachedMessageType`](#aicachedmessagetype) | The item at the end of the edge. |
| <a id="aichatmessageedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="aichatmessageedgenode"></a>`node` | [`AiChatMessage`](#aichatmessage) | The item at the end of the edge. |
#### `AiMessageTypeConnection`
@ -12578,19 +12578,21 @@ Information about a connected Agent.
| <a id="agentmetadatapodnamespace"></a>`podNamespace` | [`String`](#string) | Namespace of the pod running the Agent. |
| <a id="agentmetadataversion"></a>`version` | [`String`](#string) | Agent version tag. |
### `AiCachedMessageType`
### `AiChatMessage`
Duo Chat message.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aicachedmessagetypecontent"></a>`content` | [`String`](#string) | Content of the message. Can be null for failed responses. |
| <a id="aicachedmessagetypecontenthtml"></a>`contentHtml` | [`String`](#string) | HTML content of the message. Can be null for failed responses. |
| <a id="aicachedmessagetypeerrors"></a>`errors` | [`[String!]!`](#string) | Errors that occurred while asynchronously fetching an AI (assistant) response. |
| <a id="aicachedmessagetypeid"></a>`id` | [`ID`](#id) | UUID of the message. |
| <a id="aicachedmessagetyperequestid"></a>`requestId` | [`ID`](#id) | UUID of the original request message. |
| <a id="aicachedmessagetyperole"></a>`role` | [`AiCachedMessageRole!`](#aicachedmessagerole) | Message role. |
| <a id="aicachedmessagetypetimestamp"></a>`timestamp` | [`Time!`](#time) | Message timestamp. |
| <a id="aichatmessagecontent"></a>`content` | [`String`](#string) | Content of the message. Can be null for failed responses. |
| <a id="aichatmessagecontenthtml"></a>`contentHtml` | [`String`](#string) | Content of the message in HTML format. Can be null for failed responses. |
| <a id="aichatmessageerrors"></a>`errors` | [`[String!]!`](#string) | Errors that occurred while asynchronously fetching an AI (assistant) response. |
| <a id="aichatmessageid"></a>`id` | [`ID`](#id) | UUID of the message. |
| <a id="aichatmessagerequestid"></a>`requestId` | [`ID`](#id) | UUID of the original request message. Shared between chat prompt and response. |
| <a id="aichatmessagerole"></a>`role` | [`AiChatMessageRole!`](#aichatmessagerole) | Message role. |
| <a id="aichatmessagetimestamp"></a>`timestamp` | [`Time!`](#time) | Message timestamp. |
### `AiMessageType`
@ -12615,7 +12617,7 @@ Information about a connected Agent.
| <a id="airesponserequestid"></a>`requestId` | [`String`](#string) | ID of the original request. |
| <a id="airesponseresponsebody"></a>`responseBody` | [`String`](#string) | Response body from AI API. |
| <a id="airesponseresponsebodyhtml"></a>`responseBodyHtml` | [`String`](#string) | Response body HTML. |
| <a id="airesponserole"></a>`role` | [`AiCachedMessageRole!`](#aicachedmessagerole) | Message role. |
| <a id="airesponserole"></a>`role` | [`AiChatMessageRole!`](#aichatmessagerole) | Message role. |
| <a id="airesponsetimestamp"></a>`timestamp` | [`Time!`](#time) | Message timestamp. |
| <a id="airesponsetype"></a>`type` | [`String`](#string) | Message type. |
@ -22155,7 +22157,7 @@ Network Policies of the project.
WARNING:
**Deprecated** in 14.8.
Network policies are deprecated and will be removed in GitLab 16.0. Since GitLab 15.0 this field returns no data.
Network policies are deprecated and will be removed in GitLab 17.0. This field returns no data in GitLab 15.0 and later.
Returns [`NetworkPolicyConnection`](#networkpolicyconnection).
@ -25976,15 +25978,15 @@ Agent token statuses.
| <a id="agenttokenstatusactive"></a>`ACTIVE` | Active agent token. |
| <a id="agenttokenstatusrevoked"></a>`REVOKED` | Revoked agent token. |
### `AiCachedMessageRole`
### `AiChatMessageRole`
Roles to filter in chat message.
| Value | Description |
| ----- | ----------- |
| <a id="aicachedmessageroleassistant"></a>`ASSISTANT` | Filter only assistant messages. |
| <a id="aicachedmessagerolesystem"></a>`SYSTEM` | Filter only system messages. |
| <a id="aicachedmessageroleuser"></a>`USER` | Filter only user messages. |
| <a id="aichatmessageroleassistant"></a>`ASSISTANT` | Filter only assistant messages. |
| <a id="aichatmessagerolesystem"></a>`SYSTEM` | Filter only system messages. |
| <a id="aichatmessageroleuser"></a>`USER` | Filter only user messages. |
### `AlertManagementAlertSort`

View File

@ -784,12 +784,12 @@ The `.gitlab-ci.yml` in a downstream project:
```yaml
deploy:
script: echo "Deploy to ${CI_ENVIRONMENT_NAME} for ${CI_PROJECT_ID}"
script: echo "Deploy to ${UPSTREAM_ENVIRONMENT_NAME} for ${UPSTREAM_PROJECT_ID}"
rules:
- if: $CI_PIPELINE_SOURCE == "pipeline" && $UPSTREAM_ENVIRONMENT_ACTION == "start"
stop:
script: echo "Stop ${CI_ENVIRONMENT_NAME} for ${CI_PROJECT_ID}"
script: echo "Stop ${UPSTREAM_ENVIRONMENT_NAME} for ${UPSTREAM_PROJECT_ID}"
rules:
- if: $CI_PIPELINE_SOURCE == "pipeline" && $UPSTREAM_ENVIRONMENT_ACTION == "stop"
```

View File

@ -47,6 +47,9 @@ it's reset to a pristine test after each test.
in turn causes tests that rely on the transactions on these connections to
in turn causes tests that rely on the transactions on these connections to
fail. The issue was fixed in this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128567).
- [Example 7](https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/3389#note_1534827164):
A TCP socket used in a test was not closed before the next test, which also used
the same port with another TCP socket.
### Dataset-specific

View File

@ -59,7 +59,7 @@ module BulkImports
object.notes.each_batch(of: BATCH_SIZE) do |notes_batch|
notes_batch.each do |note|
note.refresh_markdown_cache!
enum << note if object_has_reference?(note) || object_has_username?(object)
enum << note if object_has_reference?(note) || object_has_username?(note)
end
end
end

View File

@ -53609,6 +53609,12 @@ msgstr ""
msgid "WorkItem|Key result"
msgstr ""
msgid "WorkItem|Link work items together to show that they're related or that one is blocking others."
msgstr ""
msgid "WorkItem|Linked Items"
msgstr ""
msgid "WorkItem|Mark as done"
msgstr ""

View File

@ -93,8 +93,6 @@ describe('WorkItemLinkChild', () => {
expect(findWorkItemLinkChildContents().props()).toEqual({
childItem: workItemObjectiveWithChild,
canUpdate: true,
parentWorkItemId: 'gid://gitlab/WorkItem/2',
workItemType: 'Objective',
childPath: '/gitlab-org/gitlab-test/-/work_items/12',
});
});

View File

@ -0,0 +1,25 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import WorkItemRelationships from '~/work_items/components/work_item_relationships/work_item_relationships.vue';
describe('WorkItemRelationships', () => {
let wrapper;
const createComponent = async () => {
wrapper = shallowMountExtended(WorkItemRelationships, {
propsData: {
workItem: {},
workItemIid: '1',
workItemFullpath: 'gitlab/path',
},
});
await waitForPromises();
};
it('renders the component', () => {
createComponent();
expect(wrapper.find('.work-item-relationships').exists()).toBe(true);
});
});

View File

@ -1,4 +1,4 @@
import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils';
import { autocompleteDataSources, markdownPreviewPath, workItemPath } from '~/work_items/utils';
describe('autocompleteDataSources', () => {
beforeEach(() => {
@ -25,3 +25,14 @@ describe('markdownPreviewPath', () => {
);
});
});
describe('workItemPath', () => {
it('returns corrrect data sources', () => {
expect(workItemPath('project/group', '2')).toEqual('/project/group/-/work_items/2');
});
it('returns corrrect data sources with relative url root', () => {
gon.relative_url_root = '/foobar';
expect(workItemPath('project/group', '2')).toEqual('/foobar/project/group/-/work_items/2');
});
});

View File

@ -19,7 +19,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ReferencesPipeline, feature_cat
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:issue) { create(:issue, project: project, description: 'https://my.gitlab.com/source/full/path/-/issues/1 @old_username') }
let(:issue) { create(:issue, project: project, description: 'https://my.gitlab.com/source/full/path/-/issues/1') }
let(:mr) do
create(
:merge_request,
@ -58,14 +58,37 @@ RSpec.describe BulkImports::Projects::Pipelines::ReferencesPipeline, feature_cat
)
end
let(:username_system_note) do
create(
:note,
project: project,
system: true,
noteable: issue,
note: "mentioned in merge request created by @source_username",
note_html: 'empty'
)
end
subject(:pipeline) { described_class.new(context) }
before do
project.add_owner(user)
allow(Gitlab::Cache::Import::Caching)
.to receive(:values_from_hash)
.and_return({
'old_username' => 'new_username',
'older_username' => 'newer_username',
'source_username' => 'destination_username'
})
end
def create_project_data
[issue, mr, issue_note, mr_note, system_note]
[issue, mr, issue_note, mr_note, system_note, username_system_note]
end
def create_username_project_data
[username_system_note]
end
describe '#extract' do
@ -75,12 +98,14 @@ RSpec.describe BulkImports::Projects::Pipelines::ReferencesPipeline, feature_cat
extracted_data = subject.extract(context)
expect(extracted_data).to be_instance_of(BulkImports::Pipeline::ExtractedData)
expect(extracted_data.data).to contain_exactly(issue_note, mr, issue, mr_note)
expect(extracted_data.data).to contain_exactly(issue, mr, issue_note, system_note, username_system_note, mr_note)
expect(system_note.note_html).not_to eq(old_note_html)
expect(system_note.note_html)
.to include("class=\"gfm gfm-merge_request\">!#{mr.iid}</a>")
.and include(project.full_path.to_s)
.and include("@old_username")
expect(username_system_note.note_html)
.to include("@source_username")
end
context 'when object body is nil' do
@ -95,22 +120,13 @@ RSpec.describe BulkImports::Projects::Pipelines::ReferencesPipeline, feature_cat
end
describe '#transform' do
before do
allow(Gitlab::Cache::Import::Caching)
.to receive(:values_from_hash)
.and_return({
'old_username' => 'new_username',
'older_username' => 'newer_username',
'source_username' => 'destination_username'
})
end
it 'updates matching urls and usernames with new ones' do
transformed_mr = subject.transform(context, mr)
transformed_note = subject.transform(context, mr_note)
transformed_issue = subject.transform(context, issue)
transformed_issue_note = subject.transform(context, issue_note)
transformed_system_note = subject.transform(context, system_note)
transformed_username_system_note = subject.transform(context, username_system_note)
expected_url = URI('')
expected_url.scheme = ::Gitlab.config.gitlab.https ? 'https' : 'http'
@ -118,16 +134,17 @@ RSpec.describe BulkImports::Projects::Pipelines::ReferencesPipeline, feature_cat
expected_url.port = ::Gitlab.config.gitlab.port
expected_url.path = "/#{project.full_path}/-/merge_requests/#{mr.iid}"
expect(transformed_issue.description).not_to include("@old_username")
expect(transformed_issue_note.note).not_to include("@older_username")
expect(transformed_mr.description).not_to include("@source_username")
expect(transformed_system_note.note).not_to include("@old_username")
expect(transformed_username_system_note.note).not_to include("@source_username")
expect(transformed_issue.description).to eq('http://localhost:80/namespace1/project-1/-/issues/1')
expect(transformed_mr.description).to eq("#{expected_url} @destination_username")
expect(transformed_note.note).to eq("#{expected_url} @same_username")
expect(transformed_issue.description).to include("@new_username")
expect(transformed_issue_note.note).to include("@newer_username and not_a@username")
expect(transformed_system_note.note).to eq("mentioned in merge request !#{mr.iid} created by @new_username")
expect(transformed_username_system_note.note).to include("@destination_username")
end
context 'when object does not have reference or username' do
@ -176,16 +193,6 @@ RSpec.describe BulkImports::Projects::Pipelines::ReferencesPipeline, feature_cat
end
describe '#load' do
before do
allow(Gitlab::Cache::Import::Caching)
.to receive(:values_from_hash)
.and_return({
'old_username' => 'new_username',
'older_username' => 'newer_username',
'source_username' => 'destination_username'
})
end
it 'saves the object when object body changed' do
transformed_issue = subject.transform(context, issue)
transformed_note = subject.transform(context, mr_note)