From 184b3d4a2101ec9991b2bed1cd3fa759afb5a6c4 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 5 Sep 2023 18:09:06 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- GITLAB_KAS_VERSION | 2 +- Gemfile | 2 +- Gemfile.checksum | 5 +- Gemfile.lock | 4 +- .../shared/work_item_link_child_contents.vue | 9 --- .../components/work_item_detail.vue | 17 ++++++ .../work_item_links/work_item_link_child.vue | 3 +- .../work_item_relationships.vue | 50 ++++++++++++++++ .../javascripts/work_items/constants.js | 1 + .../work_item_widgets.fragment.graphql | 4 ++ app/assets/javascripts/work_items/utils.js | 5 ++ .../projects/incidents_controller.rb | 1 + app/controllers/projects/issues_controller.rb | 1 + .../projects/work_items_controller.rb | 1 + app/controllers/projects_controller.rb | 1 + app/services/merge_requests/build_service.rb | 6 +- .../_delete_form.html.haml | 3 +- doc/api/graphql/reference/index.md | 54 +++++++++--------- doc/ci/pipelines/downstream_pipelines.md | 4 +- doc/development/testing_guide/flaky_tests.md | 3 + .../projects/pipelines/references_pipeline.rb | 2 +- locale/gitlab.pot | 6 ++ .../work_item_link_child_spec.js | 2 - .../work_item_relationships_spec.js | 25 ++++++++ spec/frontend/work_items/utils_spec.js | 13 ++++- .../pipelines/references_pipeline_spec.rb | 57 +++++++++++-------- 26 files changed, 204 insertions(+), 77 deletions(-) create mode 100644 app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue create mode 100644 spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index 0d8a8cc880d..6048cff35ce 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -v16.4.0-rc1 +v16.4.0-rc2 diff --git a/Gemfile b/Gemfile index f770046c298..fabf7e4a49f 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/Gemfile.checksum b/Gemfile.checksum index cf898be05b7..30006d24021 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -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"}, diff --git a/Gemfile.lock b/Gemfile.lock index 25437eb009c..9256ebc9b03 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue index 0a38dcb77f6..b10a3727e9f 100644 --- a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue +++ b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue @@ -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, diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 8146b66dc1f..6beca682f8f 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -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')" /> + +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'), + }, +}; + + diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 715c2ef843f..c68f59abe00 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -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'; diff --git a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql index 383d003e78c..14cb6f8415c 100644 --- a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql @@ -100,4 +100,8 @@ fragment WorkItemWidgets on WorkItemWidget { ... on WorkItemWidgetAwardEmoji { type } + + ... on WorkItemWidgetLinkedItems { + type + } } diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js index ac5d8b32fad..1443e4b509d 100644 --- a/app/assets/javascripts/work_items/utils.js +++ b/app/assets/javascripts/work_items/utils.js @@ -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); +}; diff --git a/app/controllers/projects/incidents_controller.rb b/app/controllers/projects/incidents_controller.rb index 6109e29b169..69d349b1f1d 100644 --- a/app/controllers/projects/incidents_controller.rb +++ b/app/controllers/projects/incidents_controller.rb @@ -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 diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 159e839cfec..86e914f3447 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -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] diff --git a/app/controllers/projects/work_items_controller.rb b/app/controllers/projects/work_items_controller.rb index 7da31c199a1..c3986be31b0 100644 --- a/app/controllers/projects/work_items_controller.rb +++ b/app/controllers/projects/work_items_controller.rb @@ -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 diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2ad0f11dc91..6a246219f7d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -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 diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index b24bbe1b9d9..bb347096274 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -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 diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml index fcd52f33121..76a805d4d1b 100644 --- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml +++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml @@ -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') diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 2bccafb1aeb..7c157db6b43 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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 | | ---- | ---- | ----------- | | `requestIds` | [`[ID!]`](#id) | Array of request IDs to fetch. | -| `roles` | [`[AiCachedMessageRole!]`](#aicachedmessagerole) | Array of roles to fetch. | +| `roles` | [`[AiChatMessageRole!]`](#aichatmessagerole) | Array of roles to fetch. | ### `Query.auditEventDefinitions` @@ -7775,28 +7775,28 @@ The edge type for [`AgentConfiguration`](#agentconfiguration). | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `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 | | ---- | ---- | ----------- | -| `edges` | [`[AiCachedMessageTypeEdge]`](#aicachedmessagetypeedge) | A list of edges. | -| `nodes` | [`[AiCachedMessageType]`](#aicachedmessagetype) | A list of nodes. | -| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | +| `edges` | [`[AiChatMessageEdge]`](#aichatmessageedge) | A list of edges. | +| `nodes` | [`[AiChatMessage]`](#aichatmessage) | A list of nodes. | +| `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 | | ---- | ---- | ----------- | -| `cursor` | [`String!`](#string) | A cursor for use in pagination. | -| `node` | [`AiCachedMessageType`](#aicachedmessagetype) | The item at the end of the edge. | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`AiChatMessage`](#aichatmessage) | The item at the end of the edge. | #### `AiMessageTypeConnection` @@ -12578,19 +12578,21 @@ Information about a connected Agent. | `podNamespace` | [`String`](#string) | Namespace of the pod running the Agent. | | `version` | [`String`](#string) | Agent version tag. | -### `AiCachedMessageType` +### `AiChatMessage` + +Duo Chat message. #### Fields | Name | Type | Description | | ---- | ---- | ----------- | -| `content` | [`String`](#string) | Content of the message. Can be null for failed responses. | -| `contentHtml` | [`String`](#string) | HTML content of the message. Can be null for failed responses. | -| `errors` | [`[String!]!`](#string) | Errors that occurred while asynchronously fetching an AI (assistant) response. | -| `id` | [`ID`](#id) | UUID of the message. | -| `requestId` | [`ID`](#id) | UUID of the original request message. | -| `role` | [`AiCachedMessageRole!`](#aicachedmessagerole) | Message role. | -| `timestamp` | [`Time!`](#time) | Message timestamp. | +| `content` | [`String`](#string) | Content of the message. Can be null for failed responses. | +| `contentHtml` | [`String`](#string) | Content of the message in HTML format. Can be null for failed responses. | +| `errors` | [`[String!]!`](#string) | Errors that occurred while asynchronously fetching an AI (assistant) response. | +| `id` | [`ID`](#id) | UUID of the message. | +| `requestId` | [`ID`](#id) | UUID of the original request message. Shared between chat prompt and response. | +| `role` | [`AiChatMessageRole!`](#aichatmessagerole) | Message role. | +| `timestamp` | [`Time!`](#time) | Message timestamp. | ### `AiMessageType` @@ -12615,7 +12617,7 @@ Information about a connected Agent. | `requestId` | [`String`](#string) | ID of the original request. | | `responseBody` | [`String`](#string) | Response body from AI API. | | `responseBodyHtml` | [`String`](#string) | Response body HTML. | -| `role` | [`AiCachedMessageRole!`](#aicachedmessagerole) | Message role. | +| `role` | [`AiChatMessageRole!`](#aichatmessagerole) | Message role. | | `timestamp` | [`Time!`](#time) | Message timestamp. | | `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. | `ACTIVE` | Active agent token. | | `REVOKED` | Revoked agent token. | -### `AiCachedMessageRole` +### `AiChatMessageRole` Roles to filter in chat message. | Value | Description | | ----- | ----------- | -| `ASSISTANT` | Filter only assistant messages. | -| `SYSTEM` | Filter only system messages. | -| `USER` | Filter only user messages. | +| `ASSISTANT` | Filter only assistant messages. | +| `SYSTEM` | Filter only system messages. | +| `USER` | Filter only user messages. | ### `AlertManagementAlertSort` diff --git a/doc/ci/pipelines/downstream_pipelines.md b/doc/ci/pipelines/downstream_pipelines.md index 03d8f931691..6dc58882f37 100644 --- a/doc/ci/pipelines/downstream_pipelines.md +++ b/doc/ci/pipelines/downstream_pipelines.md @@ -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" ``` diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md index 08fc9499f7a..cd5e32bc0ad 100644 --- a/doc/development/testing_guide/flaky_tests.md +++ b/doc/development/testing_guide/flaky_tests.md @@ -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 diff --git a/lib/bulk_imports/projects/pipelines/references_pipeline.rb b/lib/bulk_imports/projects/pipelines/references_pipeline.rb index de7fae6edf5..94f9f9656f0 100644 --- a/lib/bulk_imports/projects/pipelines/references_pipeline.rb +++ b/lib/bulk_imports/projects/pipelines/references_pipeline.rb @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f796e1933c7..17c6337db00 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js index 803ff950cbe..a624bbe8567 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js @@ -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', }); }); diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js new file mode 100644 index 00000000000..18d13aa251e --- /dev/null +++ b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js @@ -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); + }); +}); diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js index aa24b80cf08..8a49140119d 100644 --- a/spec/frontend/work_items/utils_spec.js +++ b/spec/frontend/work_items/utils_spec.js @@ -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'); + }); +}); diff --git a/spec/lib/bulk_imports/projects/pipelines/references_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/references_pipeline_spec.rb index 32becfa7467..8ab74905b8a 100644 --- a/spec/lib/bulk_imports/projects/pipelines/references_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/references_pipeline_spec.rb @@ -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}") .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)