Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-04 21:11:36 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-04 21:11:36 +0300
commitd2cecf8839262a058100ef8153a233a2e08979eb (patch)
treeacdcd6664003e1b04a9f87572192fc4f97dff6c6
parent0a12a556b52d7ff4a2da738f0852c78488cec9e5 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/capybara/testid_finders.yml18
-rw-r--r--app/assets/javascripts/content_editor/components/content_editor.vue6
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue2
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_text_style_dropdown.vue2
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue2
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue4
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue1
-rw-r--r--app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationship_list.vue1
-rw-r--r--app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue84
-rw-r--r--app/assets/javascripts/work_items/graphql/remove_linked_items.mutation.graphql6
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/graphql/resolvers/user_notes_count_resolver.rb2
-rw-r--r--app/models/analytics/cycle_analytics/value_stream.rb1
-rw-r--r--app/models/wiki_page.rb2
-rw-r--r--app/views/shared/_clone_panel.html.haml6
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml2
-rw-r--r--app/views/shared/empty_states/_wikis_layout.html.haml2
-rw-r--r--app/views/shared/wikis/_form.html.haml2
-rw-r--r--app/views/shared/wikis/_main_links.html.haml4
-rw-r--r--app/views/shared/wikis/_pages_wiki_page.html.haml2
-rw-r--r--app/views/shared/wikis/_sidebar.html.haml4
-rw-r--r--app/views/shared/wikis/_sidebar_wiki_page.html.haml2
-rw-r--r--app/views/shared/wikis/_wiki_content.html.haml2
-rw-r--r--app/views/shared/wikis/_wiki_directory.html.haml4
-rw-r--r--app/views/shared/wikis/show.html.haml4
-rw-r--r--config/feature_flags/development/rate_limit_oauth_api.yml8
-rw-r--r--doc/development/documentation/styleguide/index.md41
-rw-r--r--doc/development/fe_guide/frontend_goals.md13
-rw-r--r--doc/user/application_security/dependency_list/index.md1
-rw-r--r--doc/user/group/saml_sso/example_saml_config.md32
-rw-r--r--doc/user/group/saml_sso/group_sync.md8
-rw-r--r--lib/api/entities/wiki_page.rb2
-rw-r--r--lib/gitlab/rack_attack/request.rb7
-rw-r--r--locale/gitlab.pot11
-rw-r--r--package.json4
-rw-r--r--qa/qa/page/component/content_editor.rb24
-rw-r--r--qa/qa/page/component/legacy_clone_panel.rb12
-rw-r--r--qa/qa/page/component/wiki.rb36
-rw-r--r--qa/qa/page/component/wiki_page_form.rb28
-rw-r--r--qa/qa/page/component/wiki_sidebar.rb22
-rw-r--r--qa/qa/page/project/wiki/list.rb6
-rw-r--r--qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb56
-rw-r--r--spec/features/projects/work_items/linked_work_items_spec.rb28
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js9
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js81
-rw-r--r--spec/frontend/work_items/mock_data.js12
-rw-r--r--spec/graphql/resolvers/user_notes_count_resolver_spec.rb16
-rw-r--r--spec/lib/api/entities/wiki_page_spec.rb13
-rw-r--r--spec/lib/gitlab/rack_attack/request_spec.rb41
-rw-r--r--spec/models/wiki_page_spec.rb16
-rw-r--r--spec/support/helpers/features/dom_helpers.rb4
-rw-r--r--spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb12
-rw-r--r--spec/views/admin/sessions/new.html.haml_spec.rb5
-rw-r--r--yarn.lock18
57 files changed, 540 insertions, 201 deletions
diff --git a/.rubocop_todo/capybara/testid_finders.yml b/.rubocop_todo/capybara/testid_finders.yml
index a1e0c7f642e..5b22bdb75b7 100644
--- a/.rubocop_todo/capybara/testid_finders.yml
+++ b/.rubocop_todo/capybara/testid_finders.yml
@@ -1,24 +1,6 @@
---
Capybara/TestidFinders:
Exclude:
- - 'ee/spec/features/admin/admin_dev_ops_reports_spec.rb'
- - 'ee/spec/features/admin/admin_merge_requests_approvals_spec.rb'
- - 'ee/spec/features/admin/admin_sends_notification_spec.rb'
- - 'ee/spec/features/admin/admin_settings_spec.rb'
- - 'ee/spec/features/admin/geo/admin_geo_projects_spec.rb'
- - 'ee/spec/features/admin/groups/admin_subscription_alerts_spec.rb'
- - 'ee/spec/features/admin/subscriptions/admin_views_subscription_spec.rb'
- - 'ee/spec/features/billings/billing_plans_spec.rb'
- - 'ee/spec/features/boards/boards_licensed_features_spec.rb'
- - 'ee/spec/features/boards/boards_spec.rb'
- - 'ee/spec/features/boards/group_boards/board_deletion_spec.rb'
- - 'ee/spec/features/boards/new_issue_spec.rb'
- - 'ee/spec/features/boards/scoped_issue_board_spec.rb'
- - 'ee/spec/features/boards/sidebar_spec.rb'
- - 'ee/spec/features/boards/swimlanes/epics_swimlanes_drag_drop_spec.rb'
- - 'ee/spec/features/boards/swimlanes/epics_swimlanes_sidebar_labels_spec.rb'
- - 'ee/spec/features/boards/swimlanes/epics_swimlanes_sidebar_spec.rb'
- - 'ee/spec/features/boards/swimlanes/epics_swimlanes_spec.rb'
- 'ee/spec/features/boards/user_adds_lists_to_board_spec.rb'
- 'ee/spec/features/ci/ci_catalog_spec.rb'
- 'ee/spec/features/ci/ci_minutes_spec.rb'
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue
index 25c03496a76..d9314916ac4 100644
--- a/app/assets/javascripts/content_editor/components/content_editor.vue
+++ b/app/assets/javascripts/content_editor/components/content_editor.vue
@@ -238,11 +238,7 @@ export default {
@keydown="$emit('keydown', $event)"
/>
<content-editor-alert />
- <div
- data-testid="content-editor"
- data-qa-selector="content_editor_container"
- :class="{ 'is-focused': focused }"
- >
+ <div data-testid="content-editor" :class="{ 'is-focused': focused }">
<formatting-toolbar
ref="toolbar"
:supports-quick-actions="supportsQuickActions"
diff --git a/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue b/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue
index 4cf150dd948..78a01693f14 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue
@@ -58,7 +58,7 @@ export default {
name="content_editor_image"
class="gl-display-none"
:aria-label="$options.i18n.inputLabel"
- data-qa-selector="file_upload_field"
+ data-testid="file-upload-field"
@change="onFileSelect"
/>
</span>
diff --git a/app/assets/javascripts/content_editor/components/toolbar_text_style_dropdown.vue b/app/assets/javascripts/content_editor/components/toolbar_text_style_dropdown.vue
index bd30bdcea0c..4b1e14665de 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_text_style_dropdown.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_text_style_dropdown.vue
@@ -75,7 +75,7 @@ export default {
:selected="activeItemLabel"
:disabled="!activeItem"
:data-qa-text-style="activeItemLabel"
- data-qa-selector="text_style_dropdown"
+ data-testid="text-style-dropdown"
size="small"
toggle-class="btn-default-tertiary"
@select="execute"
diff --git a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
index 3792dad376b..3c070d2708d 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
@@ -77,7 +77,7 @@ export default {
v-gl-modal="$options.modal.modalId"
category="secondary"
variant="danger"
- data-qa-selector="delete_button"
+ data-qa-selector="delete-button"
>
{{ $options.i18n.deletePageText }}
</gl-button>
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
index 553cb1f0464..eaa99556994 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
@@ -317,7 +317,7 @@ export default {
name="wiki[title]"
type="text"
class="form-control"
- data-qa-selector="wiki_title_textbox"
+ data-testid="wiki-title-textbox"
:required="true"
:autofocus="!pageInfo.persisted"
:placeholder="$options.i18n.title.placeholder"
@@ -397,7 +397,7 @@ export default {
name="wiki[message]"
type="text"
class="form-control"
- data-qa-selector="wiki_message_textbox"
+ data-testid="wiki-message-textbox"
:placeholder="$options.i18n.commitMessage.label"
/>
</gl-form-group>
@@ -409,7 +409,6 @@ export default {
category="primary"
variant="confirm"
type="submit"
- data-qa-selector="wiki_submit_button"
data-testid="wiki-submit-button"
:disabled="disableSubmitButton"
>{{ submitButtonText }}</gl-button
diff --git a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
index f19dcca5166..9ffed6245ba 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue
@@ -30,7 +30,7 @@ export default {
<gl-button
:id="$options.richTextEditorButtonId"
class="btn btn-default btn-sm gl-button btn-default-tertiary gl-font-sm! gl-text-secondary! gl-px-4!"
- data-qa-selector="editing_mode_switcher"
+ data-testid="editing-mode-switcher"
@click="$emit('switch')"
>{{ text }}</gl-button
>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
index fc7e0a7c732..246dc4da801 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
@@ -292,7 +292,7 @@ export default {
class="note-textarea js-gfm-input markdown-area"
dir="auto"
:data-supports-quick-actions="supportsQuickActions"
- :data-qa-selector="formFieldProps['data-qa-selector'] || 'markdown_editor_form_field'"
+ :data-testid="formFieldProps['data-testid'] || 'markdown-editor-form-field'"
:disabled="disabled"
@input="updateMarkdownFromMarkdownField"
@keydown="$emit('keydown', $event)"
@@ -323,7 +323,7 @@ export default {
<input
v-bind="formFieldProps"
:value="markdown"
- data-qa-selector="markdown_editor_form_field"
+ data-testid="markdown-editor-form-field"
type="hidden"
/>
</div>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
index 4657954c8cc..bb36df0a778 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
@@ -268,7 +268,6 @@ export default {
class="issue-title-text"
dir="auto"
:href="webUrl"
- data-qa-selector="issuable_title_link"
data-testid="issuable-title-link"
v-bind="issuableTitleProps"
@click="handleIssuableItemClick"
diff --git a/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationship_list.vue b/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationship_list.vue
index 279e6ad01b3..cd6064d4dd0 100644
--- a/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationship_list.vue
+++ b/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationship_list.vue
@@ -53,6 +53,7 @@ export default {
:can-update="canUpdate"
:child-path="linkedItemPath(workItemFullPath, linkedItem.workItem.iid)"
@click="$emit('showModal', { event: $event, child: linkedItem.workItem })"
+ @removeChild="$emit('removeLinkedItem', linkedItem.workItem)"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue b/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue
index 6f02ea507e5..8d8aa22e544 100644
--- a/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue
+++ b/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue
@@ -1,10 +1,13 @@
<script>
-import { GlLoadingIcon, GlIcon, GlButton } from '@gitlab/ui';
+import { produce } from 'immer';
+import { GlLoadingIcon, GlIcon, GlButton, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
import groupWorkItemByIidQuery from '../../graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
+import removeLinkedItemsMutation from '../../graphql/remove_linked_items.mutation.graphql';
import { WIDGET_TYPE_LINKED_ITEMS, LINKED_CATEGORIES_MAP } from '../../constants';
import WidgetWrapper from '../widget_wrapper.vue';
@@ -12,10 +15,12 @@ import WorkItemRelationshipList from './work_item_relationship_list.vue';
import WorkItemAddRelationshipForm from './work_item_add_relationship_form.vue';
export default {
+ helpPath: helpPagePath('/user/okrs.md#linked-items-in-okrs'),
components: {
GlLoadingIcon,
GlIcon,
GlButton,
+ GlLink,
WidgetWrapper,
WorkItemRelationshipList,
WorkItemAddRelationshipForm,
@@ -124,12 +129,71 @@ export default {
hideLinkItemForm() {
this.isShownLinkItemForm = false;
},
+ async removeLinkedItem(linkedItem) {
+ try {
+ const {
+ data: {
+ workItemRemoveLinkedItems: { errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: removeLinkedItemsMutation,
+ variables: {
+ input: {
+ id: this.workItemId,
+ workItemsIds: [linkedItem.id],
+ },
+ },
+ update: (cache, { data: { workItemRemoveLinkedItems } }) => {
+ const errorMessages = workItemRemoveLinkedItems?.errors;
+ if (errorMessages && errorMessages.length > 0) {
+ [this.error] = errorMessages;
+ return;
+ }
+ const queryArgs = {
+ query: workItemByIidQuery,
+ variables: { fullPath: this.workItemFullPath, iid: this.workItemIid },
+ };
+ const sourceData = cache.readQuery(queryArgs);
+
+ if (!sourceData) {
+ return;
+ }
+
+ cache.writeQuery({
+ ...queryArgs,
+ data: produce(sourceData, (draftState) => {
+ const linkedItems =
+ draftState.workspace.workItems.nodes[0].widgets?.find(
+ (widget) => widget.type === WIDGET_TYPE_LINKED_ITEMS,
+ )?.linkedItems?.nodes || [];
+ const index = linkedItems.findIndex((item) => {
+ return item.workItem.id === linkedItem.id;
+ });
+ linkedItems.splice(index, 1);
+ }),
+ });
+ },
+ });
+
+ if (errors.length > 0) {
+ [this.error] = errors;
+ return;
+ }
+
+ this.$toast.show(s__('WorkItem|Linked item removed'));
+ } catch {
+ this.error = this.$options.i18n.removeLinkedItemErrorMessage;
+ }
+ },
},
i18n: {
title: s__('WorkItem|Linked Items'),
- fetchError: s__('WorkItem|Something went wrong when fetching tasks. Please refresh this page.'),
+ fetchError: s__('WorkItem|Something went wrong when fetching items. Please refresh this page.'),
emptyStateMessage: s__(
- "WorkItem|Link work items together to show that they're related or that one is blocking others.",
+ "WorkItem|Link items together to show that they're related or that one is blocking others.",
+ ),
+ removeLinkedItemErrorMessage: s__(
+ 'WorkItem|Something went wrong when removing item. Please refresh this page.',
),
addChildButtonLabel: s__('WorkItem|Add'),
relatedToTitle: s__('WorkItem|Related to'),
@@ -182,9 +246,12 @@ export default {
/>
<gl-loading-icon v-if="isLoading" color="dark" class="gl-my-2" />
<template v-else>
- <div v-if="isEmptyRelatedWorkItems" data-testid="links-empty">
+ <div v-if="!isShownLinkItemForm && isEmptyRelatedWorkItems" data-testid="links-empty">
<p class="gl-new-card-empty">
{{ $options.i18n.emptyStateMessage }}
+ <gl-link :href="$options.helpPath" data-testid="help-link">
+ {{ __('Learn more.') }}
+ </gl-link>
</p>
</div>
<template v-else>
@@ -197,8 +264,9 @@ export default {
:linked-items="linksBlocks"
:heading="$options.i18n.blockingTitle"
:work-item-full-path="workItemFullPath"
- :can-update="false"
+ :can-update="canAdminWorkItemLink"
@showModal="$emit('showModal', { event: $event.event, modalWorkItem: $event.child })"
+ @removeLinkedItem="removeLinkedItem"
/>
<work-item-relationship-list
v-if="linksIsBlockedBy.length"
@@ -209,16 +277,18 @@ export default {
:linked-items="linksIsBlockedBy"
:heading="$options.i18n.blockedByTitle"
:work-item-full-path="workItemFullPath"
- :can-update="false"
+ :can-update="canAdminWorkItemLink"
@showModal="$emit('showModal', { event: $event.event, modalWorkItem: $event.child })"
+ @removeLinkedItem="removeLinkedItem"
/>
<work-item-relationship-list
v-if="linksRelatesTo.length"
:linked-items="linksRelatesTo"
:heading="$options.i18n.relatedToTitle"
:work-item-full-path="workItemFullPath"
- :can-update="false"
+ :can-update="canAdminWorkItemLink"
@showModal="$emit('showModal', { event: $event.event, modalWorkItem: $event.child })"
+ @removeLinkedItem="removeLinkedItem"
/>
</template>
</template>
diff --git a/app/assets/javascripts/work_items/graphql/remove_linked_items.mutation.graphql b/app/assets/javascripts/work_items/graphql/remove_linked_items.mutation.graphql
new file mode 100644
index 00000000000..f83f5474606
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/remove_linked_items.mutation.graphql
@@ -0,0 +1,6 @@
+mutation removeLinkedItems($input: WorkItemRemoveLinkedItemsInput!) {
+ workItemRemoveLinkedItems(input: $input) {
+ errors
+ message
+ }
+}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 9f5db91bcda..031b32a712a 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -53,6 +53,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:issues_grid_view)
push_frontend_feature_flag(:service_desk_ticket)
push_frontend_feature_flag(:issues_list_drawer, project)
+ push_frontend_feature_flag(:linked_work_items, project)
end
before_action only: [:index, :show] do
diff --git a/app/graphql/resolvers/user_notes_count_resolver.rb b/app/graphql/resolvers/user_notes_count_resolver.rb
index b91815c72f5..ebc54a1c6e8 100644
--- a/app/graphql/resolvers/user_notes_count_resolver.rb
+++ b/app/graphql/resolvers/user_notes_count_resolver.rb
@@ -20,7 +20,7 @@ module Resolvers
def authorized_resource?(object)
ability = "read_#{object.class.name.underscore}".to_sym
- context[:current_user].present? && Ability.allowed?(context[:current_user], ability, object)
+ Ability.allowed?(context[:current_user], ability, object)
end
end
end
diff --git a/app/models/analytics/cycle_analytics/value_stream.rb b/app/models/analytics/cycle_analytics/value_stream.rb
index 16446a5b463..7f8c6eef704 100644
--- a/app/models/analytics/cycle_analytics/value_stream.rb
+++ b/app/models/analytics/cycle_analytics/value_stream.rb
@@ -51,3 +51,4 @@ module Analytics
end
end
end
+Analytics::CycleAnalytics::ValueStream.prepend_mod_with('Analytics::CycleAnalytics::ValueStream')
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index a7e2be0eae5..2eed693ca76 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -205,7 +205,7 @@ class WikiPage
update_attributes(attrs)
save do
- wiki.create_page(title, content, format, attrs[:message])
+ wiki.create_page(title, raw_content, format, attrs[:message])
end
end
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index dde4ec3cf52..4b39ec52837 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -5,18 +5,18 @@
%span.js-clone-dropdown-label
= enabled_protocol_button(container, enabled_protocol)
- else
- %a#clone-dropdown.input-group-text.gl-button.btn.btn-default.btn-icon.clone-dropdown-btn{ href: '#', data: { toggle: 'dropdown', qa_selector: 'clone_dropdown' } }
+ %a#clone-dropdown.input-group-text.gl-button.btn.btn-default.btn-icon.clone-dropdown-btn{ href: '#', data: { toggle: 'dropdown', testid: 'clone-dropdown' } }
%span.js-clone-dropdown-label
= default_clone_protocol.upcase
= sprite_icon('chevron-down', css_class: 'gl-icon')
- %ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown{ data: { qa_selector: 'clone_dropdown_content' } }
+ %ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown{ data: { testid: 'clone-dropdown-content' } }
%li
= ssh_clone_button(container)
%li
= http_clone_button(container)
= render_if_exists 'shared/kerberos_clone_button', container: container
- = text_field_tag :clone_url, default_url_to_repo(container), class: "js-select-on-focus btn gl-button", readonly: true, aria: { label: _('Repository clone URL') }, data: { qa_selector: 'clone_url_content' }
+ = text_field_tag :clone_url, default_url_to_repo(container), class: "js-select-on-focus btn gl-button", readonly: true, aria: { label: _('Repository clone URL') }, data: { testid: 'clone-url-content' }
.input-group-append
= clipboard_button(target: '#clone_url', title: _("Copy URL"), variant: :default, category: :primary, size: :medium)
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index 567c4a2d444..e152390b0df 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -4,7 +4,7 @@
- if !hide_create && can?(current_user, :create_wiki, @wiki.container)
- create_path = wiki_page_path(@wiki, params[:id], view: 'create')
- - create_link = link_button_to s_('WikiEmpty|Create your first page'), create_path, title: s_('WikiEmpty|Create your first page'), data: { qa_selector: 'create_first_page_link' }, variant: :confirm
+ - create_link = link_button_to s_('WikiEmpty|Create your first page'), create_path, title: s_('WikiEmpty|Create your first page'), data: { testid: 'create-first-page-link' }, variant: :confirm
= render layout: layout_path, locals: { image_path: 'illustrations/empty-state/empty-wiki-md.svg' } do
%h4.text-left
diff --git a/app/views/shared/empty_states/_wikis_layout.html.haml b/app/views/shared/empty_states/_wikis_layout.html.haml
index 03054c959fd..831bdcac073 100644
--- a/app/views/shared/empty_states/_wikis_layout.html.haml
+++ b/app/views/shared/empty_states/_wikis_layout.html.haml
@@ -1,6 +1,6 @@
.row.empty-state.empty-state-wiki
.col-12
- .svg-content.svg-150{ data: { qa_selector: 'svg_content' } }
+ .svg-content.svg-150{ data: { testid: 'svg-content' } }
= image_tag image_path
.col-12
.text-content.text-center
diff --git a/app/views/shared/wikis/_form.html.haml b/app/views/shared/wikis/_form.html.haml
index 34bedbd928a..cdf4b50a99d 100644
--- a/app/views/shared/wikis/_form.html.haml
+++ b/app/views/shared/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-- page_info = { last_commit_sha: @page.last_commit_sha, persisted: @page.persisted?, title: @page.title, content: @page.content || '', format: @page.format.to_s, uploads_path: uploads_path, path: wiki_page_path(@wiki, @page), wiki_path: wiki_path(@wiki), help_path: help_page_path('user/project/wiki/index'), markdown_help_path: help_page_path('user/markdown'), markdown_preview_path: wiki_page_path(@wiki, @page, action: :preview_markdown), create_path: wiki_path(@wiki, action: :create) }
+- page_info = { last_commit_sha: @page.last_commit_sha, persisted: @page.persisted?, title: @page.title, content: @page.raw_content || '', format: @page.format.to_s, uploads_path: uploads_path, path: wiki_page_path(@wiki, @page), wiki_path: wiki_path(@wiki), help_path: help_page_path('user/project/wiki/index'), markdown_help_path: help_page_path('user/markdown'), markdown_preview_path: wiki_page_path(@wiki, @page, action: :preview_markdown), create_path: wiki_path(@wiki, action: :create) }
.gl-mt-3
= form_errors(@page, truncate: :title)
diff --git a/app/views/shared/wikis/_main_links.html.haml b/app/views/shared/wikis/_main_links.html.haml
index 41831c95198..9a76069e8f6 100644
--- a/app/views/shared/wikis/_main_links.html.haml
+++ b/app/views/shared/wikis/_main_links.html.haml
@@ -1,6 +1,6 @@
- if @page&.persisted?
- = link_button_to wiki_page_path(@wiki, @page, action: :history), role: "button", data: { qa_selector: 'page_history_button' } do
+ = link_button_to wiki_page_path(@wiki, @page, action: :history), role: "button", data: { testid: 'page-history-button' } do
= s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @wiki.container)
- = link_button_to wiki_path(@wiki, action: :new), role: "button", data: { qa_selector: 'new_page_button' }, variant: :confirm, category: :secondary do
+ = link_button_to wiki_path(@wiki, action: :new), role: "button", data: { testid: 'new-page-button' }, variant: :confirm, category: :secondary do
= s_("Wiki|New page")
diff --git a/app/views/shared/wikis/_pages_wiki_page.html.haml b/app/views/shared/wikis/_pages_wiki_page.html.haml
index fb6f58d044d..23931bbbc32 100644
--- a/app/views/shared/wikis/_pages_wiki_page.html.haml
+++ b/app/views/shared/wikis/_pages_wiki_page.html.haml
@@ -1,5 +1,5 @@
%li
- = link_to wiki_page.human_title, wiki_page_path(@wiki, wiki_page), data: { qa_selector: 'wiki_page_link', qa_page_name: wiki_page.slug }
+ = link_to wiki_page.human_title, wiki_page_path(@wiki, wiki_page), data: { testid: 'wiki-page-link', qa_page_name: wiki_page.slug }
%small (#{wiki_page.format})
.float-right
- if wiki_page.last_version
diff --git a/app/views/shared/wikis/_sidebar.html.haml b/app/views/shared/wikis/_sidebar.html.haml
index a34827602ab..cd752d31643 100644
--- a/app/views/shared/wikis/_sidebar.html.haml
+++ b/app/views/shared/wikis/_sidebar.html.haml
@@ -8,7 +8,7 @@
.gl-display-flex.gl-flex-wrap
- git_access_url = wiki_path(@wiki, action: :git_access)
- = link_to git_access_url, class: 'gl-mr-5' + (active_nav_link?(path: 'wikis#git_access') ? ' active' : ''), data: { qa_selector: 'clone_repository_link' } do
+ = link_to git_access_url, class: 'gl-mr-5' + (active_nav_link?(path: 'wikis#git_access') ? ' active' : ''), data: { testid: 'clone-repository-link' } do
= sprite_icon('download', css_class: 'gl-mr-2')
%span= _("Clone repository")
@@ -32,5 +32,5 @@
= render partial: entry.to_partial_path, object: entry, locals: { context: 'sidebar' }
.block.w-100
- if @sidebar_limited
- = link_button_to wiki_path(@wiki, action: :pages), data: { qa_selector: 'view_all_pages_button' }, block: true do
+ = link_button_to wiki_path(@wiki, action: :pages), data: { testid: 'view-all-pages-button' }, block: true do
= s_("Wiki|View All Pages")
diff --git a/app/views/shared/wikis/_sidebar_wiki_page.html.haml b/app/views/shared/wikis/_sidebar_wiki_page.html.haml
index 2c5c3aa68a3..710ecf6196e 100644
--- a/app/views/shared/wikis/_sidebar_wiki_page.html.haml
+++ b/app/views/shared/wikis/_sidebar_wiki_page.html.haml
@@ -3,5 +3,5 @@
%li{ class: active_when(params[:id] == wiki_page.slug) }
.gl-relative.gl-display-flex.gl-align-items-center.js-wiki-list-toggle.wiki-list{ data: { testid: 'wiki-list' } }
= render Pajamas::ButtonComponent.new(icon: 'plus', href: "#{wiki_path}/{new_page_title}", button_options: { class: 'wiki-list-create-child-button gl-bg-transparent! gl-hover-bg-gray-50! gl-focus-bg-gray-50! gl-absolute gl-top-half gl-translate-y-n50 gl-cursor-pointer gl-right-3' })
- = link_to wiki_path, data: { qa_selector: 'wiki_page_link', qa_page_name: wiki_page.human_title } do
+ = link_to wiki_path, data: { testid: 'wiki-page-link', qa_page_name: wiki_page.human_title } do
= wiki_page.human_title
diff --git a/app/views/shared/wikis/_wiki_content.html.haml b/app/views/shared/wikis/_wiki_content.html.haml
index 780e4c4746d..b5210b340f3 100644
--- a/app/views/shared/wikis/_wiki_content.html.haml
+++ b/app/views/shared/wikis/_wiki_content.html.haml
@@ -1,2 +1,2 @@
-.js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json } }
+.js-wiki-page-content.md.gl-pt-2{ data: { testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json } }
= render_wiki_content(@page)
diff --git a/app/views/shared/wikis/_wiki_directory.html.haml b/app/views/shared/wikis/_wiki_directory.html.haml
index 6a066e0a838..cce81257691 100644
--- a/app/views/shared/wikis/_wiki_directory.html.haml
+++ b/app/views/shared/wikis/_wiki_directory.html.haml
@@ -1,11 +1,11 @@
- wiki_path = wiki_page_path(@wiki, wiki_directory)
-%li{ class: active_when(params[:id] == wiki_directory.slug), data: { qa_selector: 'wiki_directory_content' } }
+%li{ class: active_when(params[:id] == wiki_directory.slug), data: { testid: 'wiki-directory-content' } }
.gl-relative.gl-display-flex.gl-align-items-center.js-wiki-list-toggle.wiki-list{ data: { testid: 'wiki-list' } }<
= sprite_icon('chevron-right', css_class: 'js-wiki-list-expand-button wiki-list-expand-button gl-mr-2 gl-cursor-pointer')
= sprite_icon('chevron-down', css_class: 'js-wiki-list-collapse-button wiki-list-collapse-button gl-mr-2 gl-cursor-pointer')
= render Pajamas::ButtonComponent.new(icon: 'plus', href: "#{wiki_path}/{new_page_title}", button_options: { class: 'wiki-list-create-child-button gl-bg-transparent! gl-hover-bg-gray-50! gl-focus-bg-gray-50! gl-absolute gl-top-half gl-translate-y-n50 gl-cursor-pointer gl-right-3' })
- = link_to wiki_path, data: { qa_selector: 'wiki_dir_page_link', qa_page_name: wiki_directory.title } do
+ = link_to wiki_path, data: { testid: 'wiki-dir-page-link', qa_page_name: wiki_directory.title } do
= wiki_directory.title
%ul
- wiki_directory.entries.each do |entry|
diff --git a/app/views/shared/wikis/show.html.haml b/app/views/shared/wikis/show.html.haml
index be1f43f44de..9537d6fec15 100644
--- a/app/views/shared/wikis/show.html.haml
+++ b/app/views/shared/wikis/show.html.haml
@@ -29,10 +29,10 @@
.gl-mt-5.gl-mb-3
.gl-display-flex.gl-justify-content-space-between
- %h2.gl-mt-0.gl-mb-5{ data: { qa_selector: 'wiki_page_title', testid: 'wiki_page_title' } }= @page.human_title
+ %h2.gl-mt-0.gl-mb-5{ data: { testid: 'wiki-page-title' } }= @page.human_title
%div
- if can?(current_user, :create_wiki, @wiki.container) && @page.latest? && @valid_encoding
- = render Pajamas::ButtonComponent.new(href: wiki_page_path(@wiki, @page, action: :edit), icon: 'pencil', button_options: { class: 'js-wiki-edit', title: "Edit", data: { qa_selector: 'edit_page_button', testid: 'wiki_edit_button' }})
+ = render Pajamas::ButtonComponent.new(href: wiki_page_path(@wiki, @page, action: :edit), icon: 'pencil', button_options: { class: 'js-wiki-edit', title: "Edit", data: { testid: 'wiki-edit-button' }})
.js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
diff --git a/config/feature_flags/development/rate_limit_oauth_api.yml b/config/feature_flags/development/rate_limit_oauth_api.yml
new file mode 100644
index 00000000000..bf4db5aa617
--- /dev/null
+++ b/config/feature_flags/development/rate_limit_oauth_api.yml
@@ -0,0 +1,8 @@
+---
+name: rate_limit_oauth_api
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133109
+rollout_issue_url:
+milestone: '16.5'
+type: development
+group: group::authentication and authorization
+default_enabled: false
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index ba702101019..74300e8c81a 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -1980,3 +1980,44 @@ It renders as:
```
::EndTabs
+
+### Changes for a version upgrade
+
+To document upgrade notes and changes, create a new page for each major version of GitLab.
+For an example, see [GitLab 16 changes](../../../update/versions/gitlab_16_changes.md).
+Use the following template to add information to the page.
+
+```markdown
+# GitLab X changes **(FREE SELF)**
+
+This page contains upgrade information for minor and patch versions of GitLab X. Review these instructions for:
+
+- Your installation type.
+- All versions between your current version and your target version.
+
+For more information about upgrading GitLab Helm Chart, see [the release notes for X.0](https://docs.gitlab.com/charts/releases/X_0.html).
+
+## X.Y.1 (add the latest version at the top of the page)
+
+- General upgrade notes and issues.
+- ...
+
+### Linux package installations
+
+- Information specific to Linux package installations.
+- ...
+
+### Self-compiled installations
+
+- Information specific to self-compiled installations.
+- ...
+
+### Geo installations **(PREMIUM SELF)**
+
+ - Information specific to Geo.
+ - ...
+
+## X.Y.0
+
+ ...
+```
diff --git a/doc/development/fe_guide/frontend_goals.md b/doc/development/fe_guide/frontend_goals.md
index c25e858ae7a..4f39e82c72e 100644
--- a/doc/development/fe_guide/frontend_goals.md
+++ b/doc/development/fe_guide/frontend_goals.md
@@ -29,3 +29,16 @@ The realistic goal is to move to _multiple SPAs_ experience where we define the
All of them have the same context (project path, current user etc.), we could easily fetch more data with issue-specific parameter (issue `iid`) and store the results on the client (so that opening the same issue won't require more API calls). This leads to a smooth user experience for navigating through issues.
For navigation between clusters, we can still rely on Rails routing. These cases should be relatively more scarce than navigation within clusters.
+
+## Reusable components
+
+Currently, we keep generically reusable components in two main places:
+
+- GitLab UI
+- `vue_shared` folder
+
+While GitLab UI is well-documented and components are abstract enough to be reused anywhere in Vue applications, our `vue_shared` components are somewhat chaotic, often can be used only in certain context (for example, they can be bound to an existing Vuex store) and have duplicates (we have multiple components for notes).
+
+We should perform an audit of `vue_shared`, find out what can and what cannot be moved to GitLab UI, and refactor existing components to remove duplicates and increase reusability. The ideal outcome would be having application-specific components moved to application folders, and keep reusable "smart" components in the shared folder/library, ensuring that every single piece of reusable functionality has _only one implementation_.
+
+This is currently under development. Follow the [GitLab Modular Monolith for FE](https://gitlab.com/gitlab-org/gitlab/-/issues/422903) for updates on how we will enforce encapsulation on top-level folders like `vue_shared`.
diff --git a/doc/user/application_security/dependency_list/index.md b/doc/user/application_security/dependency_list/index.md
index d8726cbd456..91145b10f81 100644
--- a/doc/user/application_security/dependency_list/index.md
+++ b/doc/user/application_security/dependency_list/index.md
@@ -10,6 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - System dependencies [introduced](https://gitlab.com/groups/gitlab-org/-/epics/6698) in GitLab 14.6.
> - Group-level dependency list [introduced](https://gitlab.com/groups/gitlab-org/-/epics/8090) in GitLab 16.2 [with a flag](../../../administration/feature_flags.md) named `group_level_dependencies`. Disabled by default.
> - Group-level dependency list [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/411257) in GitLab 16.4.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132015) in GitLab 16.5. Feature flag `group_level_dependencies` removed.
Use the dependency list to review your project or group's dependencies and key details about those
dependencies, including their known vulnerabilities. This list is a collection of dependencies in your
diff --git a/doc/user/group/saml_sso/example_saml_config.md b/doc/user/group/saml_sso/example_saml_config.md
index 70e01e1d9a9..c69546f710e 100644
--- a/doc/user/group/saml_sso/example_saml_config.md
+++ b/doc/user/group/saml_sso/example_saml_config.md
@@ -50,7 +50,7 @@ Provisioning:
![Azure AD SCIM Provisioning](img/AzureAD-scim_provisioning.png)
-Attribute mapping:
+### Attribute mapping
![Azure AD SCIM Attribute Mapping](img/AzureAD-scim_attribute_mapping.png)
@@ -70,15 +70,15 @@ If available, you can add user-friendly group names instead. When setting up Azu
## Google Workspace
-Basic SAML app configuration:
+### Basic SAML app configuration
![Google Workspace basic SAML](img/GoogleWorkspace-basic-SAML_v14_10.png)
-User claims and attributes:
+### User claims and attributes
![Google Workspace user claims](img/GoogleWorkspace-claims_v14_10.png)
-IdP links and certificate:
+### IdP links and certificate
NOTE:
Google Workspace displays a SHA256 fingerprint. To retrieve the SHA1 fingerprint required by GitLab for configuring SAML, download the certificate and calculate the SHA1 certificate
@@ -88,53 +88,55 @@ fingerprint.
## Okta
-Basic SAML app configuration for GitLab.com groups:
+### Basic SAML app configuration for GitLab.com groups
![Okta basic SAML](img/Okta-GroupSAML.png)
-Basic SAML app configuration for GitLab self-managed:
+### Basic SAML app configuration for GitLab self-managed
![Okta admin panel view](img/Okta-SM.png)
-User claims and attributes:
+### User claims and attributes
![Okta Attributes](img/Okta-attributes.png)
-Groups attribute:
+### Group Sync
![Okta Group attribute](img/Okta-GroupAttribute.png)
-Advanced SAML app settings (defaults):
+### Advanced SAML app settings (defaults)
![Okta Advanced Settings](img/Okta-advancedsettings.png)
-IdP Links and Certificate:
+### IdP links and certificate
![Okta Links and Certificate](img/Okta-linkscert.png)
-Sign on settings:
+### SAML sign on settings
![Okta SAML settings](img/okta_saml_settings.png)
+### SCIM settings
+
Setting the username for the newly provisioned users when assigning them the SCIM app:
![Assigning SCIM app to users on Okta](img/okta_setting_username.png)
## OneLogin
-Application details:
+### Basic SAML app configuration
![OneLogin application details](img/OneLogin-app_details.png)
-Parameters:
+### Parameters
![OneLogin application details](img/OneLogin-parameters.png)
-Adding a user:
+### Adding a user
![OneLogin user add](img/OneLogin-userAdd.png)
-SSO settings:
+### SSO settings
![OneLogin SSO settings](img/OneLogin-SSOsettings.png)
diff --git a/doc/user/group/saml_sso/group_sync.md b/doc/user/group/saml_sso/group_sync.md
index 4002472e760..7641c04de10 100644
--- a/doc/user/group/saml_sso/group_sync.md
+++ b/doc/user/group/saml_sso/group_sync.md
@@ -69,9 +69,11 @@ For example, Azure AD sends the Azure Group Object ID instead of the name. Use t
```
Other attribute names such as `http://schemas.microsoft.com/ws/2008/06/identity/claims/groups`
-are not accepted as a source of groups. For more information on configuring the
-required attribute name in the SAML identity provider's settings, see
-[example group SAML and SCIM configurations](../../../user/group/saml_sso/example_saml_config.md).
+are not accepted as a source of groups.
+
+For more information on configuring the
+required group attribute name in the SAML identity provider's settings, see
+example configurations for [Azure AD](../../../user/group/saml_sso/example_saml_config.md#group-sync) and [Okta](../../../user/group/saml_sso/example_saml_config.md#group-sync-1).
## Configure SAML Group Links
diff --git a/lib/api/entities/wiki_page.rb b/lib/api/entities/wiki_page.rb
index 9d2a031cee8..0f3fdd586a3 100644
--- a/lib/api/entities/wiki_page.rb
+++ b/lib/api/entities/wiki_page.rb
@@ -15,7 +15,7 @@ module API
current_user: options[:current_user]
)
else
- wiki_page.content
+ wiki_page.raw_content
end
end
diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb
index 03ead81fd1c..e45782b8be0 100644
--- a/lib/gitlab/rack_attack/request.rb
+++ b/lib/gitlab/rack_attack/request.rb
@@ -5,6 +5,7 @@ module Gitlab
module Request
include ::Gitlab::Utils::StrongMemoize
+ API_PATH_REGEX = %r{^/api/|/oauth/}
FILES_PATH_REGEX = %r{^/api/v\d+/projects/[^/]+/repository/files/.+}
GROUP_PATH_REGEX = %r{^/api/v\d+/groups/[^/]+/?$}
@@ -32,7 +33,11 @@ module Gitlab
end
def api_request?
- logical_path.start_with?('/api')
+ if ::Feature.enabled?(:rate_limit_oauth_api, ::Feature.current_request)
+ matches?(API_PATH_REGEX)
+ else
+ logical_path.start_with?('/api')
+ end
end
def logical_path
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 15cdfcca05b..addd7dd46a6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -53784,12 +53784,15 @@ msgstr ""
msgid "WorkItem|Key result"
msgstr ""
-msgid "WorkItem|Link work items together to show that they're related or that one is blocking others."
+msgid "WorkItem|Link items together to show that they're related or that one is blocking others."
msgstr ""
msgid "WorkItem|Linked Items"
msgstr ""
+msgid "WorkItem|Linked item removed"
+msgstr ""
+
msgid "WorkItem|Mark as done"
msgstr ""
@@ -53874,6 +53877,9 @@ msgstr ""
msgid "WorkItem|Something went wrong when deleting the task. Please try again."
msgstr ""
+msgid "WorkItem|Something went wrong when fetching items. Please refresh this page."
+msgstr ""
+
msgid "WorkItem|Something went wrong when fetching iterations. Please try again."
msgstr ""
@@ -53889,6 +53895,9 @@ msgstr ""
msgid "WorkItem|Something went wrong when fetching work items. Please try again."
msgstr ""
+msgid "WorkItem|Something went wrong when removing item. Please refresh this page."
+msgstr ""
+
msgid "WorkItem|Something went wrong when trying to add a child. Please try again."
msgstr ""
diff --git a/package.json b/package.json
index 41ec8339baa..0e58b667356 100644
--- a/package.json
+++ b/package.json
@@ -52,8 +52,8 @@
"@apollo/client": "^3.5.10",
"@babel/core": "^7.18.5",
"@babel/preset-env": "^7.18.2",
- "@cubejs-client/core": "^0.33.59",
- "@cubejs-client/vue": "^0.33.65",
+ "@cubejs-client/core": "^0.34.0",
+ "@cubejs-client/vue": "^0.34.0",
"@floating-ui/dom": "^1.2.9",
"@gitlab/application-sdk-browser": "^0.2.8",
"@gitlab/at.js": "1.5.7",
diff --git a/qa/qa/page/component/content_editor.rb b/qa/qa/page/component/content_editor.rb
index f2733e4b065..d5f93bbc8c9 100644
--- a/qa/qa/page/component/content_editor.rb
+++ b/qa/qa/page/component/content_editor.rb
@@ -10,41 +10,41 @@ module QA
super
base.view 'app/assets/javascripts/content_editor/components/content_editor.vue' do
- element :content_editor_container
+ element 'content-editor'
end
- base.view 'app/assets/javascripts/content_editor/components/toolbar_text_style_dropdown.vue' do
- element :text_style_dropdown
+ base.view 'app/assets/javascripts/content_editor/components/formatting_toolbar.vue' do
+ element 'text-styles'
end
base.view 'app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue' do
- element :file_upload_field
+ element 'file-upload-field'
end
base.view 'app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue' do
- element :markdown_editor_form_field
+ element 'markdown-editor-form-field'
end
end
def add_heading(heading, text)
- within_element(:content_editor_container) do
+ within_element('content-editor') do
text_area.set(text)
# wait for text style option to become active after typing
- has_active_element?(:text_style_dropdown, wait: 1)
- click_element(:text_style_dropdown)
- find_element(:text_style_dropdown).find('li', text: heading).click
+ has_active_element?('text-styles', wait: 1)
+ click_element('text-styles')
+ find_element('.gl-new-dropdown-contents li', text: heading).click
end
end
def upload_image(image_path)
- within_element(:content_editor_container) do
+ within_element('content-editor') do
# add image on a new line
text_area.send_keys(:return)
- find_element(:file_upload_field, visible: false).send_keys(image_path)
+ find_element('file-upload-field', visible: false).send_keys(image_path)
end
QA::Support::Retrier.retry_on_exception do
- source = find_element(:markdown_editor_form_field, visible: false)
+ source = find_element('markdown-editor-form-field', visible: false)
source.value =~ %r{uploads/.*#{::File.basename(image_path)}}
end
end
diff --git a/qa/qa/page/component/legacy_clone_panel.rb b/qa/qa/page/component/legacy_clone_panel.rb
index 8c3c25f6e41..2534865c2f4 100644
--- a/qa/qa/page/component/legacy_clone_panel.rb
+++ b/qa/qa/page/component/legacy_clone_panel.rb
@@ -10,9 +10,9 @@ module QA
super
base.view 'app/views/shared/_clone_panel.html.haml' do
- element :clone_dropdown
- element :clone_dropdown_content
- element :clone_url_content
+ element 'clone-dropdown'
+ element 'clone-dropdown-content'
+ element 'clone-url-content'
end
end
@@ -28,16 +28,16 @@ module QA
end
def repository_location
- Git::Location.new(find_element(:clone_url_content).value)
+ Git::Location.new(find_element('clone-url-content').value)
end
private
def choose_repository_clone(kind, detect_text)
wait_until(reload: false) do
- click_element :clone_dropdown
+ click_element 'clone-dropdown'
- within_element(:clone_dropdown_content) do
+ within_element('clone-dropdown-content') do
click_link(kind)
end
diff --git a/qa/qa/page/component/wiki.rb b/qa/qa/page/component/wiki.rb
index e34e1f4b589..d5daaf7d8ea 100644
--- a/qa/qa/page/component/wiki.rb
+++ b/qa/qa/page/component/wiki.rb
@@ -10,25 +10,25 @@ module QA
super
base.view 'app/views/shared/wikis/show.html.haml' do
- element :wiki_page_title
- element :edit_page_button
+ element 'wiki-page-title'
+ element 'wiki-edit-button'
end
base.view 'app/views/shared/wikis/_wiki_content.html.haml' do
- element :wiki_page_content
+ element 'wiki-page-content'
end
base.view 'app/views/shared/wikis/_main_links.html.haml' do
- element :new_page_button
- element :page_history_button
+ element 'new-page-button'
+ element 'page-history-button'
end
base.view 'app/views/shared/empty_states/_wikis.html.haml' do
- element :create_first_page_link
+ element 'create-first-page-link'
end
base.view 'app/views/shared/empty_states/_wikis_layout.html.haml' do
- element :svg_content
+ element 'svg-content'
end
end
@@ -37,49 +37,49 @@ module QA
# "Create your first page" button shifts up a bit. This can cause
# webdriver to miss the hit so we wait for the svg to load before
# clicking the button.
- within_element(:svg_content) do
+ within_element('svg-content') do
has_element?('js-lazy-loaded-content')
end
- click_element(:create_first_page_link)
+ click_element('create-first-page-link')
end
def click_new_page
- click_element(:new_page_button)
+ click_element('new-page-button')
end
def click_page_history
- click_element(:page_history_button)
+ click_element('page-history-button')
end
def click_edit
- click_element(:edit_page_button)
+ click_element('wiki-edit-button')
end
def has_title?(title)
- has_element?(:wiki_page_title, title)
+ has_element?('wiki-page-title', title)
end
def has_content?(content)
- has_element?(:wiki_page_content, content)
+ has_element?('wiki-page-content', content)
end
def has_no_content?(content)
- has_no_element?(:wiki_page_content, content)
+ has_no_element?('wiki-page-content', content)
end
def has_no_page?
- has_element?(:create_first_page_link)
+ has_element?('create-first-page-link')
end
def has_heading?(heading_type, text)
- within_element(:wiki_page_content) do
+ within_element('wiki-page-content') do
has_css?(heading_type, text: text)
end
end
def has_image?(image_file_name)
- within_element(:wiki_page_content) do
+ within_element('wiki-page-content') do
has_css?("img[src$='#{image_file_name}']")
end
end
diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb
index 335790c5b27..454121b457e 100644
--- a/qa/qa/page/component/wiki_page_form.rb
+++ b/qa/qa/page/component/wiki_page_form.rb
@@ -10,34 +10,34 @@ module QA
super
base.view 'app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue' do
- element :wiki_title_textbox
- element :wiki_message_textbox
- element :wiki_submit_button
+ element 'wiki-title-textbox'
+ element 'wiki-message-textbox'
+ element 'wiki-submit-button'
end
base.view 'app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue' do
- element :markdown_editor_form_field
+ element 'markdown-editor-form-field'
end
base.view 'app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue' do
- element :editing_mode_switcher
+ element 'editing-mode-switcher'
end
base.view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
- element :delete_button
+ element 'delete-button'
end
end
def set_title(title)
- fill_element(:wiki_title_textbox, title)
+ fill_element('wiki-title-textbox', title)
end
def set_content(content)
- fill_element(:markdown_editor_form_field, content)
+ fill_element('markdown-editor-form-field', content)
end
def set_message(message)
- fill_element(:wiki_message_textbox, message)
+ fill_element('wiki-message-textbox', message)
end
def click_submit
@@ -45,23 +45,23 @@ module QA
# before clicking submit. See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97693#note_1098728562
sleep 0.5
- click_element(:wiki_submit_button)
+ click_element('wiki-submit-button')
QA::Support::Retrier.retry_on_exception do
- has_no_element?(:wiki_title_textbox)
+ has_no_element?('wiki-title-textbox')
end
end
def delete_page
- click_element(:delete_button, Page::Modal::DeleteWiki)
+ click_element('delete-button', Page::Modal::DeleteWiki)
Page::Modal::DeleteWiki.perform(&:confirm_deletion)
end
def use_new_editor
- click_element(:editing_mode_switcher)
+ click_element('editing-mode-switcher')
wait_until(reload: false) do
- has_element?(:content_editor_container)
+ has_element?('content-editor')
end
end
end
diff --git a/qa/qa/page/component/wiki_sidebar.rb b/qa/qa/page/component/wiki_sidebar.rb
index 7543b9655f9..8c9741c3086 100644
--- a/qa/qa/page/component/wiki_sidebar.rb
+++ b/qa/qa/page/component/wiki_sidebar.rb
@@ -10,42 +10,42 @@ module QA
super
base.view 'app/views/shared/wikis/_sidebar.html.haml' do
- element :clone_repository_link
- element :view_all_pages_button
+ element 'clone-repository-link'
+ element 'view-all-pages-button'
end
base.view 'app/views/shared/wikis/_sidebar_wiki_page.html.haml' do
- element :wiki_page_link
+ element 'wiki-page-link'
end
base.view 'app/views/shared/wikis/_wiki_directory.html.haml' do
- element :wiki_directory_content
- element :wiki_dir_page_link
+ element 'wiki-directory-content'
+ element 'wiki-dir-page-link'
end
end
def click_clone_repository
- click_element(:clone_repository_link)
+ click_element('clone-repository-link')
end
def click_view_all_pages
- click_element(:view_all_pages_button)
+ click_element('view-all-pages-button')
end
def click_page_link(page_title)
- click_element(:wiki_page_link, page_name: page_title)
+ click_element('wiki-page-link', page_name: page_title)
end
def has_page_listed?(page_title)
- has_element?(:wiki_page_link, page_name: page_title)
+ has_element?('wiki-page-link', page_name: page_title)
end
def has_directory?(directory)
- has_element?(:wiki_directory_content, text: directory)
+ has_element?('wiki-directory-content', text: directory)
end
def has_dir_page?(dir_page)
- has_element?(:wiki_dir_page_link, page_name: dir_page)
+ has_element?('wiki-dir-page-link', page_name: dir_page)
end
end
end
diff --git a/qa/qa/page/project/wiki/list.rb b/qa/qa/page/project/wiki/list.rb
index 785847011bf..224199256e1 100644
--- a/qa/qa/page/project/wiki/list.rb
+++ b/qa/qa/page/project/wiki/list.rb
@@ -6,15 +6,15 @@ module QA
module Wiki
class List < Base
view 'app/views/shared/wikis/_pages_wiki_page.html.haml' do
- element :wiki_page_link
+ element 'wiki-page-link'
end
def click_page_link(page_title)
- click_element :wiki_page_link, page_name: page_title
+ click_element 'wiki-page-link', page_name: page_title
end
def has_page_listed?(page_title)
- has_element? :wiki_page_link, page_name: page_title
+ has_element? 'wiki-page-link', page_name: page_title
end
end
end
diff --git a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
index 7be18f9d949..959d691bad7 100644
--- a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
@@ -14,6 +14,7 @@ module QA
describe 'Project import', product_group: :import_and_integrate do # rubocop:disable RSpec/MultipleMemoizedHelpers
let(:github_repo) { ENV['QA_LARGE_IMPORT_REPO'] || 'rspec/rspec-core' }
let(:import_max_duration) { ENV['QA_LARGE_IMPORT_DURATION']&.to_i || 7200 }
+ let(:api_parallel_threads) { ENV['QA_LARGE_IMPORT_GH_API_PARALLEL']&.to_i || Etc.nprocessors }
let(:logger) { Runtime::Logger.logger }
let(:differ) { RSpec::Support::Differ.new(color: true) }
let(:gitlab_address) { QA::Runtime::Scenario.gitlab_address.chomp("/") }
@@ -359,11 +360,8 @@ module QA
comments: [*gh_pr_comments[id], *gh_issue_comments[id]].compact
}
end
- logger.info("- Fetching pr events 8 parallel threads -")
- Parallel.map(prs, in_threads: 8) do |id, pr|
- logger.debug("Fetching events for pr !#{id}")
- [id, pr.merge({ events: fetch_github_events(id) })]
- end.to_h
+
+ fetch_github_events(prs, "pr")
end
end
@@ -383,22 +381,26 @@ module QA
comments: gh_issue_comments[id]
}
end
- logger.info("- Fetching issue events in 8 parallel threads -")
- Parallel.map(issues, in_threads: 8) do |id, issue|
- logger.debug("Fetching events for issue !#{id}")
- [id, issue.merge({ events: fetch_github_events(id) })]
- end.to_h
+
+ fetch_github_events(issues, "issue")
end
end
- # Fetch github events for issue/pr
+ # Fetch github events and add to issue object
#
- # @param [Integer] id
- # @return [Array]
- def fetch_github_events(id)
- with_paginated_request { github_client.issue_events(github_repo, id) }
- .map { |event| event[:event] }
- .reject { |event| unsupported_events.include?(event) }
+ # @param [Hash] issuables
+ # @param [String] type
+ # @return [Hash]
+ def fetch_github_events(issuables, type)
+ logger.info("- Fetching #{type} events in #{api_parallel_threads} parallel threads -")
+ Parallel.map(issuables, in_threads: 8) do |id, issuable|
+ logger.debug("[tid: #{current_thread}] Fetching events for #{type} !#{id}")
+ events = with_paginated_request { github_client.issue_events(github_repo, id) }
+ .map { |event| event[:event] }
+ .reject { |event| unsupported_events.include?(event) }
+
+ [id, issuable.merge({ events: events })]
+ end.to_h
end
# Verify imported mrs or issues and return missing items
@@ -530,8 +532,8 @@ module QA
logger.debug("= Fetching merge requests =")
imported_mrs = imported_project.merge_requests(**api_request_params)
- logger.debug("- Fetching merge request comments #{Etc.nprocessors} parallel threads -")
- Parallel.map(imported_mrs, in_threads: Etc.nprocessors) do |mr|
+ logger.debug("- Fetching merge request comments #{api_parallel_threads} parallel threads -")
+ Parallel.map(imported_mrs, in_threads: api_parallel_threads) do |mr|
resource = Resource::MergeRequest.init do |resource|
resource.project = imported_project
resource.iid = mr[:iid]
@@ -562,8 +564,8 @@ module QA
logger.debug("= Fetching issues =")
imported_issues = imported_project.issues(**api_request_params)
- logger.debug("- Fetching issue comments #{Etc.nprocessors} parallel threads -")
- Parallel.map(imported_issues, in_threads: Etc.nprocessors) do |issue|
+ logger.debug("- Fetching issue comments #{api_parallel_threads} parallel threads -")
+ Parallel.map(imported_issues, in_threads: api_parallel_threads) do |issue|
resource = build(:issue, project: imported_project, iid: issue[:iid], api_client: api_client)
comments = resource.comments(**api_request_params)
@@ -666,7 +668,7 @@ module QA
next_link = github_client.last_response.rels[:next]&.href
break unless next_link
- logger.debug("Fetching resources from next page: '#{next_link}'")
+ logger.debug("[tid: #{current_thread}] Fetching resources from next page: '#{next_link}'")
resources.concat(with_rate_limit { github_client.get(next_link) })
end
@@ -682,11 +684,19 @@ module QA
raise e unless e.response[:status] == 403
wait = github_client.rate_limit.resets_in + 5
- logger.warn("GitHub rate api rate limit reached, resuming in '#{wait}' seconds")
+ logger.warn("[tid: #{current_thread}] GitHub rate api rate limit reached, resuming in '#{wait}' seconds")
+ logger.debug("[tid: #{current_thread}] #{JSON.parse(e.response[:body])['message']}")
sleep(wait)
retry
end
+
+ # Get current thread id for better logging
+ #
+ # @return [Integer]
+ def current_thread
+ Thread.current.object_id
+ end
end
end
end
diff --git a/spec/features/projects/work_items/linked_work_items_spec.rb b/spec/features/projects/work_items/linked_work_items_spec.rb
index c071a7501f2..1fa2f66e71e 100644
--- a/spec/features/projects/work_items/linked_work_items_spec.rb
+++ b/spec/features/projects/work_items/linked_work_items_spec.rb
@@ -82,5 +82,33 @@ RSpec.describe 'Work item linked items', :js, feature_category: :team_planning d
expect(find('.work-items-list')).to have_content('Task 1')
end
end
+
+ it 'removes a linked item', :aggregate_failures do
+ page.within('.work-item-relationships') do
+ click_button 'Add'
+
+ within_testid('link-work-item-form') do
+ expect(page).to have_button('Add', disabled: true)
+ find_by_testid('work-item-token-select-input').set(task.title)
+ wait_for_all_requests
+ click_button task.title
+
+ expect(page).to have_button('Add', disabled: false)
+
+ click_button 'Add'
+
+ wait_for_all_requests
+ end
+
+ expect(find('.work-items-list')).to have_content('Task 1')
+
+ find_by_testid('links-menu').click
+ click_button 'Remove'
+
+ wait_for_all_requests
+
+ expect(page).not_to have_content('Task 1')
+ end
+ end
end
end
diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js
index 4a5d9bb1175..edf0fcee904 100644
--- a/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js
+++ b/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlForm, GlFormRadioGroup, GlAlert } from '@gitlab/ui';
-import { s__ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -61,12 +60,12 @@ describe('WorkItemAddRelationshipForm', () => {
it('renders link work item form with default values', () => {
expect(findLinkWorkItemForm().exists()).toBe(true);
expect(findRadioGroup().props('options')).toEqual([
- { text: s__('WorkItem|relates to'), value: LINKED_ITEM_TYPE_VALUE.RELATED },
- { text: s__('WorkItem|blocks'), value: LINKED_ITEM_TYPE_VALUE.BLOCKS },
- { text: s__('WorkItem|is blocked by'), value: LINKED_ITEM_TYPE_VALUE.BLOCKED_BY },
+ { text: 'relates to', value: LINKED_ITEM_TYPE_VALUE.RELATED },
+ { text: 'blocks', value: LINKED_ITEM_TYPE_VALUE.BLOCKS },
+ { text: 'is blocked by', value: LINKED_ITEM_TYPE_VALUE.BLOCKED_BY },
]);
expect(findLinkWorkItemButton().attributes('disabled')).toBe('true');
- expect(findMaxWorkItemNote().text()).toBe(s__('WorkItem|Add a maximum of 3 items at a time.'));
+ expect(findMaxWorkItemNote().text()).toBe('Add a maximum of 3 items at a time.');
});
it('renders work item token input with default props', () => {
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
index 83f4cf5081e..7178fa1aae7 100644
--- 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
@@ -12,12 +12,14 @@ import WorkItemRelationshipList from '~/work_items/components/work_item_relation
import WorkItemAddRelationshipForm from '~/work_items/components/work_item_relationships/work_item_add_relationship_form.vue';
import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
+import removeLinkedItemsMutation from '~/work_items/graphql/remove_linked_items.mutation.graphql';
import {
groupWorkItemByIidResponseFactory,
workItemByIidResponseFactory,
mockLinkedItems,
mockBlockingLinkedItem,
+ removeLinkedWorkItemResponse,
} from '../../mock_data';
describe('WorkItemRelationships', () => {
@@ -30,17 +32,30 @@ describe('WorkItemRelationships', () => {
const groupWorkItemsQueryHandler = jest
.fn()
.mockResolvedValue(groupWorkItemByIidResponseFactory());
+ const removeLinkedWorkItemSuccessMutationHandler = jest
+ .fn()
+ .mockResolvedValue(removeLinkedWorkItemResponse('Successfully unlinked IDs: 2.'));
+ const removeLinkedWorkItemErrorMutationHandler = jest
+ .fn()
+ .mockResolvedValue(removeLinkedWorkItemResponse(null, ['Linked item removal failed']));
+ const $toast = {
+ show: jest.fn(),
+ };
const createComponent = async ({
workItemQueryHandler = emptyLinkedWorkItemsQueryHandler,
workItemType = 'Task',
isGroup = false,
+ removeLinkedWorkItemMutationHandler = removeLinkedWorkItemSuccessMutationHandler,
} = {}) => {
+ const mockApollo = createMockApollo([
+ [workItemByIidQuery, workItemQueryHandler],
+ [removeLinkedItemsMutation, removeLinkedWorkItemMutationHandler],
+ [groupWorkItemByIidQuery, groupWorkItemsQueryHandler],
+ ]);
+
wrapper = shallowMountExtended(WorkItemRelationships, {
- apolloProvider: createMockApollo([
- [workItemByIidQuery, workItemQueryHandler],
- [groupWorkItemByIidQuery, groupWorkItemsQueryHandler],
- ]),
+ apolloProvider: mockApollo,
propsData: {
workItemId: 'gid://gitlab/WorkItem/1',
workItemIid: '1',
@@ -50,6 +65,9 @@ describe('WorkItemRelationships', () => {
provide: {
isGroup,
},
+ mocks: {
+ $toast,
+ },
});
await waitForPromises();
@@ -59,6 +77,7 @@ describe('WorkItemRelationships', () => {
const findWidgetWrapper = () => wrapper.findComponent(WidgetWrapper);
const findEmptyRelatedMessageContainer = () => wrapper.findByTestId('links-empty');
const findLinkedItemsCountContainer = () => wrapper.findByTestId('linked-items-count');
+ const findLinkedItemsHelpLink = () => wrapper.findByTestId('help-link');
const findAllWorkItemRelationshipListComponents = () =>
wrapper.findAllComponents(WorkItemRelationshipList);
const findAddButton = () => wrapper.findByTestId('link-item-add-button');
@@ -77,6 +96,9 @@ describe('WorkItemRelationships', () => {
expect(findEmptyRelatedMessageContainer().exists()).toBe(true);
expect(findAddButton().exists()).toBe(true);
expect(findWorkItemRelationshipForm().exists()).toBe(false);
+ expect(findLinkedItemsHelpLink().attributes('href')).toBe(
+ '/help/user/okrs.md#linked-items-in-okrs',
+ );
});
it('renders blocking linked item lists', async () => {
@@ -158,4 +180,55 @@ describe('WorkItemRelationships', () => {
expect(groupWorkItemsQueryHandler).toHaveBeenCalled();
});
});
+
+ it('removes linked item and shows toast message when removeLinkedItem event is emitted', async () => {
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems })),
+ });
+
+ expect(findLinkedItemsCountContainer().text()).toBe('3');
+
+ await findAllWorkItemRelationshipListComponents()
+ .at(0)
+ .vm.$emit('removeLinkedItem', { id: 'gid://gitlab/WorkItem/2' });
+
+ await waitForPromises();
+
+ expect(removeLinkedWorkItemSuccessMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: 'gid://gitlab/WorkItem/1',
+ workItemsIds: ['gid://gitlab/WorkItem/2'],
+ },
+ });
+
+ expect($toast.show).toHaveBeenCalledWith('Linked item removed');
+
+ expect(findLinkedItemsCountContainer().text()).toBe('2');
+ });
+
+ it.each`
+ errorType | mutationMock | errorMessage
+ ${'an error in the mutation response'} | ${removeLinkedWorkItemErrorMutationHandler} | ${'Linked item removal failed'}
+ ${'a network error'} | ${jest.fn().mockRejectedValue(new Error('Network Error'))} | ${'Something went wrong when removing item. Please refresh this page.'}
+ `(
+ 'shows an error message when there is $errorType while removing items',
+ async ({ mutationMock, errorMessage }) => {
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems })),
+ removeLinkedWorkItemMutationHandler: mutationMock,
+ });
+
+ await findAllWorkItemRelationshipListComponents()
+ .at(0)
+ .vm.$emit('removeLinkedItem', { id: 'gid://gitlab/WorkItem/2' });
+
+ await waitForPromises();
+
+ expect(findWidgetWrapper().props('error')).toBe(errorMessage);
+ },
+ );
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 9cd76c0651e..820802e92d9 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -3484,6 +3484,18 @@ export const linkedWorkItemResponse = (options, errors = []) => {
};
};
+export const removeLinkedWorkItemResponse = (message, errors = []) => {
+ return {
+ data: {
+ workItemRemoveLinkedItems: {
+ errors,
+ message,
+ __typename: 'WorkItemRemoveLinkedItemsPayload',
+ },
+ },
+ };
+};
+
export const groupWorkItemsQueryResponse = {
data: {
group: {
diff --git a/spec/graphql/resolvers/user_notes_count_resolver_spec.rb b/spec/graphql/resolvers/user_notes_count_resolver_spec.rb
index b3368d532b2..810dfc9c324 100644
--- a/spec/graphql/resolvers/user_notes_count_resolver_spec.rb
+++ b/spec/graphql/resolvers/user_notes_count_resolver_spec.rb
@@ -27,6 +27,14 @@ RSpec.describe Resolvers::UserNotesCountResolver do
it 'returns the number of non-system notes for the issue' do
expect(subject).to eq(2)
end
+
+ context 'when not logged in' do
+ let(:user) { nil }
+
+ it 'returns the number of non-system notes for the issue' do
+ expect(subject).to eq(2)
+ end
+ end
end
context 'when a user has permission to view notes' do
@@ -65,6 +73,14 @@ RSpec.describe Resolvers::UserNotesCountResolver do
it 'returns the number of non-system notes for the merge request' do
expect(subject).to eq(2)
end
+
+ context 'when not logged in' do
+ let(:user) { nil }
+
+ it 'returns the number of non-system notes for the merge request' do
+ expect(subject).to eq(2)
+ end
+ end
end
context 'when a user has permission to view notes' do
diff --git a/spec/lib/api/entities/wiki_page_spec.rb b/spec/lib/api/entities/wiki_page_spec.rb
index c75bba12484..a3566293c5c 100644
--- a/spec/lib/api/entities/wiki_page_spec.rb
+++ b/spec/lib/api/entities/wiki_page_spec.rb
@@ -22,6 +22,19 @@ RSpec.describe API::Entities::WikiPage do
expect(subject[:content]).to eq wiki_page.content
end
+ context "with front matter content" do
+ let(:wiki_page) { create(:wiki_page) }
+ let(:content_with_front_matter) { "---\nxxx: abc\n---\nHome Page" }
+
+ before do
+ wiki_page.update(content: content_with_front_matter) # rubocop:disable Rails/SaveBang
+ end
+
+ it 'returns the raw wiki page content' do
+ expect(subject[:content]).to eq content_with_front_matter
+ end
+ end
+
context 'when render_html param is passed' do
context 'when it is true' do
let(:params) { { render_html: true } }
diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb
index 9d2144f75db..92c9acb83cf 100644
--- a/spec/lib/gitlab/rack_attack/request_spec.rb
+++ b/spec/lib/gitlab/rack_attack/request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::RackAttack::Request do
+RSpec.describe Gitlab::RackAttack::Request, feature_category: :rate_limiting do
using RSpec::Parameterized::TableSyntax
let(:path) { '/' }
@@ -38,8 +38,12 @@ RSpec.describe Gitlab::RackAttack::Request do
'/groups' | false
'/foo/api' | false
- '/api' | true
+ '/api' | false
+ '/api/' | true
'/api/v4/groups/1' | true
+
+ '/oauth/tokens' | true
+ '/oauth/userinfo' | true
end
with_them do
@@ -53,6 +57,36 @@ RSpec.describe Gitlab::RackAttack::Request do
it { is_expected.to eq(expected) }
end
end
+
+ context 'when rate_limit_oauth_api feature flag is disabled' do
+ before do
+ stub_feature_flags(rate_limit_oauth_api: false)
+ end
+
+ where(:path, :expected) do
+ '/' | false
+ '/groups' | false
+ '/foo/api' | false
+
+ '/api' | true
+ '/api/v4/groups/1' | true
+
+ '/oauth/tokens' | false
+ '/oauth/userinfo' | false
+ end
+
+ with_them do
+ it { is_expected.to eq(expected) }
+
+ context 'when the application is mounted at a relative URL' do
+ before do
+ stub_config_setting(relative_url_root: '/gitlab/root')
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+ end
+ end
end
describe '#api_internal_request?' do
@@ -196,7 +230,8 @@ RSpec.describe Gitlab::RackAttack::Request do
'/groups' | true
'/foo/api' | true
- '/api' | false
+ '/api' | true
+ '/api/' | false
'/api/v4/groups/1' | false
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index ee61f191f05..2e1cb9d3d9b 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -394,6 +394,22 @@ RSpec.describe WikiPage, feature_category: :wiki do
expect { subject.create(title: '') }.not_to change { wiki.list_pages.length }
end
end
+
+ context "with front matter context" do
+ let(:attributes) do
+ {
+ title: SecureRandom.hex,
+ content: "---\nxxx: abc\n---\nHome Page",
+ format: "markdown",
+ message: 'Custom Commit Message'
+ }
+ end
+
+ it 'create the page with front matter' do
+ subject.create(attributes)
+ expect(wiki.find_page(title).front_matter).to eq({ xxx: "abc" })
+ end
+ end
end
describe "dot in the title" do
diff --git a/spec/support/helpers/features/dom_helpers.rb b/spec/support/helpers/features/dom_helpers.rb
index ac6523f3360..258a8c1ada9 100644
--- a/spec/support/helpers/features/dom_helpers.rb
+++ b/spec/support/helpers/features/dom_helpers.rb
@@ -2,8 +2,8 @@
module Features
module DomHelpers
- def find_by_testid(testid)
- page.find("[data-testid='#{testid}']")
+ def find_by_testid(testid, **kwargs)
+ page.find("[data-testid='#{testid}']", **kwargs)
end
def within_testid(testid, &block)
diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
index 05eab2953d7..784de102f4f 100644
--- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb
@@ -45,7 +45,7 @@ RSpec.shared_examples 'User updates wiki page' do
first(:link, text: 'three').click
- expect(find('[data-testid="wiki_page_title"]')).to have_content('three')
+ expect(find('[data-testid="wiki-page-title"]')).to have_content('three')
click_on('Edit')
diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
index 767caffd417..254682e1a3a 100644
--- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
+++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb
@@ -57,7 +57,7 @@ RSpec.shared_examples 'User views a wiki page' do
first(:link, text: 'three').click
- expect(find('[data-testid="wiki_page_title"]')).to have_content('three')
+ expect(find('[data-testid="wiki-page-title"]')).to have_content('three')
click_on('Edit')
@@ -122,7 +122,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'shows the page history' do
visit(wiki_page_path(wiki, wiki_page))
- expect(page).to have_selector('[data-testid="wiki_edit_button"]')
+ expect(page).to have_selector('[data-testid="wiki-edit-button"]')
click_on('Page history')
@@ -134,7 +134,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'does not show the "Edit" button' do
visit(wiki_page_path(wiki, wiki_page, version_id: wiki_page.versions.last.id))
- expect(page).not_to have_selector('[data-testid="wiki_edit_button"]')
+ expect(page).not_to have_selector('[data-testid="wiki-edit-button"]')
end
context 'show the diff' do
@@ -209,7 +209,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'preserves the special characters' do
visit(wiki_page_path(wiki, wiki_page))
- expect(page).to have_css('[data-testid="wiki_page_title"]', text: title)
+ expect(page).to have_css('[data-testid="wiki-page-title"]', text: title)
expect(page).to have_css('.wiki-pages li', text: title)
end
end
@@ -224,7 +224,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'safely displays the page' do
visit(wiki_page_path(wiki, wiki_page))
- expect(page).to have_selector('[data-testid="wiki_page_title"]', text: title)
+ expect(page).to have_selector('[data-testid="wiki-page-title"]', text: title)
expect(page).to have_content('foo bar')
end
end
@@ -251,7 +251,7 @@ RSpec.shared_examples 'User views a wiki page' do
end
it 'does not show "Edit" button' do
- expect(page).not_to have_selector('[data-testid="wiki_edit_button"]')
+ expect(page).not_to have_selector('[data-testid="wiki-edit-button"]')
end
it 'shows error' do
diff --git a/spec/views/admin/sessions/new.html.haml_spec.rb b/spec/views/admin/sessions/new.html.haml_spec.rb
index c1ed8d4f4ef..81275fa8750 100644
--- a/spec/views/admin/sessions/new.html.haml_spec.rb
+++ b/spec/views/admin/sessions/new.html.haml_spec.rb
@@ -36,14 +36,15 @@ RSpec.describe 'admin/sessions/new.html.haml' do
context 'omniauth authentication enabled' do
before do
allow(view).to receive(:omniauth_enabled?).and_return(true)
- allow(view).to receive(:button_based_providers_enabled?).and_return(true)
+ allow(view).to receive(:password_authentication_enabled_for_web?).and_return(true)
end
it 'shows omniauth form' do
render
expect(rendered).not_to have_content _('No authentication methods configured.')
- expect(rendered).to have_content _('or')
+ expect(rendered).to have_css('.omniauth-divider')
+ expect(rendered).to have_content(_('or sign in with'))
expect(rendered).to have_css('.omniauth-container')
end
end
diff --git a/yarn.lock b/yarn.lock
index 1fe9e2a3169..9bd7969f657 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1025,10 +1025,10 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz#798622546b63847e82389e473fd67f2707d82247"
integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==
-"@cubejs-client/core@^0.33.59":
- version "0.33.59"
- resolved "https://registry.yarnpkg.com/@cubejs-client/core/-/core-0.33.59.tgz#985ec795ff94411f508a4c28b0f92347f1eaafc6"
- integrity sha512-BNJnxDPYrLjiZU+OAp+qL/twHVdjKPH8aeVDx3oxrk8GR7hA9xN2bjoJxScdGqUNwtyXn9a9iu/tzsXcut0IBQ==
+"@cubejs-client/core@^0.34.0":
+ version "0.34.0"
+ resolved "https://registry.yarnpkg.com/@cubejs-client/core/-/core-0.34.0.tgz#f02504619a77be1fb70c3faf0b2576f975c9f05d"
+ integrity sha512-kzwpdPruuZrCiKRmO69G92TdXTb5mZvW2vyvdW6v71avYO10698cM5hivgeENz2bCeLkwKrc1Vx9KihZnDNr1Q==
dependencies:
"@babel/runtime" "^7.1.2"
core-js "^3.6.5"
@@ -1038,12 +1038,12 @@
url-search-params-polyfill "^7.0.0"
uuid "^8.3.2"
-"@cubejs-client/vue@^0.33.65":
- version "0.33.65"
- resolved "https://registry.yarnpkg.com/@cubejs-client/vue/-/vue-0.33.65.tgz#1b1f371393173c2cb2fe23a1be4e8d91bf240083"
- integrity sha512-vzFxMGlYrBxa+7fIEqG17mZqqKPSE83a+I1incb67ICqabN1lG8yw23Es/sl+mkM50/UprKF3kM48vfpyBK0KQ==
+"@cubejs-client/vue@^0.34.0":
+ version "0.34.0"
+ resolved "https://registry.yarnpkg.com/@cubejs-client/vue/-/vue-0.34.0.tgz#a61c1e6139b298ac19c1955b05ca2950efd82cfd"
+ integrity sha512-UYHRWbGufxt4AkB4CPP9D/+MRI9TTOi37GHJKBPux8DMSUNyR9u6pre+VPRAdgSRJk6LYUoENTAvaxRfytdnmQ==
dependencies:
- "@cubejs-client/core" "^0.33.59"
+ "@cubejs-client/core" "^0.34.0"
core-js "^3.6.5"
ramda "^0.27.2"