summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-05 06:08:05 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-05 06:08:05 +0000
commitb4d76c5ac78ec9f690e2094fbf5f77331b9432c8 (patch)
treeedae40dc2654f4508a4e776b809867979d0af075
parentb1646969577dbafca1b5936c3aa9535ae17d8558 (diff)
downloadgitlab-ce-b4d76c5ac78ec9f690e2094fbf5f77331b9432c8.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/content_editor/extensions/diagram.js5
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js14
-rw-r--r--app/assets/stylesheets/page_bundles/admin/geo_nodes.scss45
-rw-r--r--app/assets/stylesheets/page_bundles/admin/geo_replicable.scss18
-rw-r--r--app/controllers/search_controller.rb5
-rw-r--r--app/helpers/search_helper.rb36
-rw-r--r--app/views/projects/_fork_suggestion.html.haml5
-rw-r--r--app/views/projects/blob/_editor.html.haml14
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml7
-rw-r--r--app/views/projects/project_templates/_project_fields_form.html.haml2
-rw-r--r--app/views/shared/blob/_markdown_buttons.html.haml11
-rw-r--r--app/views/shared/issuable/_feed_buttons.html.haml8
-rw-r--r--app/views/shared/projects/_search_form.html.haml2
-rw-r--r--config/application.rb2
-rw-r--r--config/feature_flags/development/search_page_vertical_nav.yml8
-rw-r--r--doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md6
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb2
-rw-r--r--spec/fixtures/markdown/markdown_golden_master_examples.yml8
-rw-r--r--spec/frontend/__helpers__/dom_shims/index.js1
-rw-r--r--spec/frontend/__helpers__/dom_shims/text_encoder.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/pkce_spec.js4
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js12
-rw-r--r--spec/helpers/search_helper_spec.rb285
24 files changed, 470 insertions, 40 deletions
diff --git a/app/assets/javascripts/content_editor/extensions/diagram.js b/app/assets/javascripts/content_editor/extensions/diagram.js
index d9983b8c1c5..7c4a56468eb 100644
--- a/app/assets/javascripts/content_editor/extensions/diagram.js
+++ b/app/assets/javascripts/content_editor/extensions/diagram.js
@@ -1,5 +1,6 @@
import { lowlight } from 'lowlight/lib/core';
import { textblockTypeInputRule } from '@tiptap/core';
+import { base64DecodeUnicode } from '~/lib/utils/text_utility';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
import languageLoader from '../services/code_block_language_loader';
import CodeBlockHighlight from './code_block_highlight';
@@ -45,7 +46,9 @@ export default CodeBlockHighlight.extend({
priority: PARSE_HTML_PRIORITY_HIGHEST,
tag: '[data-diagram]',
getContent(element, schema) {
- const source = atob(element.dataset.diagramSrc.replace('data:text/plain;base64,', ''));
+ const source = base64DecodeUnicode(
+ element.dataset.diagramSrc.replace('data:text/plain;base64,', ''),
+ );
const node = schema.node('paragraph', {}, [schema.text(source)]);
return node.content;
},
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 59645d50e29..367180714df 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,5 +1,5 @@
import { isString, memoize } from 'lodash';
-
+import { base64ToBuffer, bufferToBase64 } from '~/authentication/webauthn/util';
import {
TRUNCATE_WIDTH_DEFAULT_WIDTH,
TRUNCATE_WIDTH_DEFAULT_FONT_SIZE,
@@ -513,3 +513,15 @@ export const limitedCounterWithDelimiter = (count) => {
return count > limit ? '1,000+' : count;
};
+
+// Encoding UTF8 ⇢ base64
+export function base64EncodeUnicode(str) {
+ const encoder = new TextEncoder('utf8');
+ return bufferToBase64(encoder.encode(str));
+}
+
+// Decoding base64 ⇢ UTF8
+export function base64DecodeUnicode(str) {
+ const decoder = new TextDecoder('utf8');
+ return decoder.decode(base64ToBuffer(str));
+}
diff --git a/app/assets/stylesheets/page_bundles/admin/geo_nodes.scss b/app/assets/stylesheets/page_bundles/admin/geo_nodes.scss
new file mode 100644
index 00000000000..b0aaa48569a
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/admin/geo_nodes.scss
@@ -0,0 +1,45 @@
+@import '../mixins_and_variables_and_functions';
+
+.geo-node-header-grid-columns {
+ grid-template-columns: 1fr auto;
+ grid-gap: $gl-spacing-scale-5;
+
+ @include media-breakpoint-up(md) {
+ grid-template-columns: 3fr 1fr;
+ }
+}
+
+.geo-node-details-grid-columns {
+ grid-gap: $gl-spacing-scale-5;
+
+ @include media-breakpoint-up(lg) {
+ grid-template-columns: 1fr 3fr;
+ }
+}
+
+.geo-node-core-details-grid-columns {
+ grid-template-columns: 1fr 1fr;
+ grid-gap: $gl-spacing-scale-5;
+}
+
+.geo-node-replication-details-grid-columns {
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 1rem;
+
+ @include media-breakpoint-up(md) {
+ grid-template-columns: 1fr 1fr 2fr 2fr;
+ }
+}
+
+.geo-node-filter-grid-columns {
+ grid-template-columns: 1fr;
+
+ @include media-breakpoint-up(md) {
+ grid-template-columns: 3fr 1fr;
+ }
+}
+
+.geo-node-replication-counts-grid {
+ grid-template-columns: 2fr 1fr 1fr;
+ grid-gap: 1rem;
+}
diff --git a/app/assets/stylesheets/page_bundles/admin/geo_replicable.scss b/app/assets/stylesheets/page_bundles/admin/geo_replicable.scss
new file mode 100644
index 00000000000..691d4abd195
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/admin/geo_replicable.scss
@@ -0,0 +1,18 @@
+@import '../mixins_and_variables_and_functions';
+
+.geo-replicable-item-grid {
+ grid-template-columns: 8ch 1fr auto;
+ grid-gap: 1rem;
+}
+
+.geo-replicable-filter-grid {
+ grid-template-columns: 1fr;
+
+ @include media-breakpoint-up(md) {
+ grid-template-columns: 2fr 1fr;
+ }
+
+ @include media-breakpoint-up(xl) {
+ grid-template-columns: 1fr 1fr;
+ }
+}
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 9f87ad6aaf6..b8bef385d04 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -242,6 +242,11 @@ class SearchController < ApplicationController
def search_type
'basic'
end
+
+ before_action do
+ # Prefer to scope it per project or user e.g.
+ push_frontend_feature_flag(:search_page_vertical_nav, current_user)
+ end
end
SearchController.prepend_mod_with('SearchController')
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index b16235893ae..8ecb625a0fa 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -380,6 +380,42 @@ module SearchHelper
end
end
+ def search_filter_link_json(scope, label, data, search)
+ search_params = params.merge(search).merge({ scope: scope }).permit(SEARCH_GENERIC_PARAMS)
+ active_scope = @scope == scope
+
+ result = { label: label, scope: scope, data: data, link: search_path(search_params), active: active_scope }
+ result[:count] = @search_results.formatted_count(scope) if active_scope && !@timeout
+ result[:count_link] = search_count_path(search_params) unless active_scope
+
+ result
+ end
+
+ # search page scope navigation
+ def search_navigation
+ {
+ projects: { label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? },
+ blobs: { label: _("Code"), data: { qa_selector: 'code_tab' }, condition: project_search_tabs?(:blobs) || search_service.show_elasticsearch_tabs? || feature_flag_tab_enabled?(:global_search_code_tab) },
+ epics: { label: _("Epics"), condition: @project.nil? && search_service.show_epics? },
+ issues: { label: _("Issues"), condition: project_search_tabs?(:issues) || feature_flag_tab_enabled?(:global_search_issues_tab) },
+ merge_requests: { label: _("Merge requests"), condition: project_search_tabs?(:merge_requests) || feature_flag_tab_enabled?(:global_search_merge_requests_tab) },
+ wiki_blobs: { label: _("Wiki"), condition: project_search_tabs?(:wiki) || search_service.show_elasticsearch_tabs? },
+ commits: { label: _("Commits"), condition: project_search_tabs?(:commits) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)) },
+ notes: { label: _("Comments"), condition: project_search_tabs?(:notes) || search_service.show_elasticsearch_tabs? },
+ milestones: { label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? },
+ users: { label: _("Users"), condition: show_user_search_tab? },
+ snippet_titles: { label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
+ }
+ end
+
+ def search_navigation_json
+ result = {}
+ search_navigation.each do |scope, nav|
+ result[scope] = search_filter_link_json(scope.to_s, nav[:label], nav[:data], nav[:search]) if nav[:condition]
+ end
+ result.to_json
+ end
+
def search_filter_input_options(type, placeholder = _('Search or filter results...'))
opts =
{
diff --git a/app/views/projects/_fork_suggestion.html.haml b/app/views/projects/_fork_suggestion.html.haml
index 55e609c0ffb..47d60593b4a 100644
--- a/app/views/projects/_fork_suggestion.html.haml
+++ b/app/views/projects/_fork_suggestion.html.haml
@@ -2,6 +2,7 @@
- message = message_base.html_safe % { edit_start: '<span class="js-file-fork-suggestion-section-action">'.html_safe, edit_end: '</span>'.html_safe }
.js-file-fork-suggestion-section.file-fork-suggestion.hidden
%span.file-fork-suggestion-note= message
- = link_to s_('ForkSuggestion|Fork'), nil, method: :post, class: 'js-fork-suggestion-button gl-button btn btn-grouped btn-confirm-secondary'
- %button.js-cancel-fork-suggestion-button.gl-button.btn.btn-grouped{ type: 'button' }
+ = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, category: :secondary, button_options: { class: "js-fork-suggestion-button btn-grouped" }) do
+ = s_('ForkSuggestion|Fork')
+ = render Pajamas::ButtonComponent.new(button_options: { class: "js-cancel-fork-suggestion-button btn-grouped" }) do
= s_('ForkSuggestion|Cancel')
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index a47a490d7c5..bd08ab67cd3 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -29,15 +29,11 @@
.file-buttons.gl-display-flex.gl-align-items-center.gl-justify-content-end
- if is_markdown
= render 'shared/blob/markdown_buttons', show_fullscreen_button: false, supports_file_upload: false
- = button_tag class: 'soft-wrap-toggle btn gl-button btn-default', type: 'button', tabindex: '-1' do
- .no-wrap
- = sprite_icon('soft-unwrap', css_class: 'gl-button-icon')
- %span.gl-button-text
- No wrap
- .soft-wrap
- = sprite_icon('soft-wrap', css_class: 'gl-button-icon')
- %span.gl-button-text
- Soft wrap
+ %span.soft-wrap-toggle
+ = render Pajamas::ButtonComponent.new(icon: 'soft-unwrap', button_options: { class: 'no-wrap' }) do
+ = _("No wrap")
+ = render Pajamas::ButtonComponent.new(icon: 'soft-wrap', button_options: { class: 'soft-wrap' }) do
+ = _("Soft wrap")
.file-editor.code
- if Feature.enabled?(:source_editor_toolbar, current_user)
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
index 00d12423eb9..1efea6a1d37 100644
--- a/app/views/projects/merge_requests/_nav_btns.html.haml
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -5,7 +5,8 @@
.js-csv-import-export-buttons{ data: { show_export_button: "true", issuable_type: issuable_type, issuable_count: issuables_count_for_state(issuable_type.to_sym, params[:state]), email: notification_email, export_csv_path: export_csv_project_merge_requests_path(@project, request.query_parameters), container_class: 'gl-mr-3' } }
- if @can_bulk_update
- = button_tag _("Edit merge requests"), class: "gl-button btn btn-default gl-mr-3 js-bulk-update-toggle"
+ = render Pajamas::ButtonComponent.new(type: :submit, button_options: { class: 'gl-mr-3 js-bulk-update-toggle' }) do
+ = _("Edit merge requests")
- if merge_project
- = link_to new_merge_request_path, class: "gl-button btn btn-confirm", title: _("New merge request") do
- = _('New merge request')
+ = render Pajamas::ButtonComponent.new(href: new_merge_request_path, variant: :confirm) do
+ = _("New merge request")
diff --git a/app/views/projects/project_templates/_project_fields_form.html.haml b/app/views/projects/project_templates/_project_fields_form.html.haml
index 7908550ca88..c3528b421b9 100644
--- a/app/views/projects/project_templates/_project_fields_form.html.haml
+++ b/app/views/projects/project_templates/_project_fields_form.html.haml
@@ -8,5 +8,5 @@
.selected-icon.gl-mr-3
.selected-template
.input-group-append
- %button.btn.gl-button.btn-default.change-template{ type: "button" }
+ = render Pajamas::ButtonComponent.new(button_options: { class: 'change-template' }) do
= _('Change template')
diff --git a/app/views/shared/blob/_markdown_buttons.html.haml b/app/views/shared/blob/_markdown_buttons.html.haml
index ac64e659901..63ba8a2e0f9 100644
--- a/app/views/shared/blob/_markdown_buttons.html.haml
+++ b/app/views/shared/blob/_markdown_buttons.html.haml
@@ -37,11 +37,8 @@
title: _("Add a collapsible section") })
= markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: _("Add a table") })
- if supports_file_upload
- %button.gl-button.btn.btn-default-tertiary.btn-icon.has-tooltip.js-attach-file-button{ type: 'button',
- title: _("Attach a file or image"),
- aria: { label: _("Attach a file or image") },
- data: { testid: 'button-attach-file', container: 'body' } }
- = sprite_icon('paperclip')
+ = render Pajamas::ButtonComponent.new(icon: 'paperclip', category: :tertiary, button_options: { 'aria-label': _("Attach a file or image"), class: 'has-tooltip js-attach-file-button', data: { testid: 'button-attach-file', container: 'body' } }) do
+ = _("Attach a file or image")
- if show_fullscreen_button
- %button.gl-button.btn.btn-default-tertiary.btn-icon.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: _("Go full screen"), data: { container: "body" } }
- = sprite_icon("maximize")
+ = render Pajamas::ButtonComponent.new(icon: 'maximize', category: :tertiary, button_options: { 'tabindex': -1, 'aria-label': _("Go full screen"), class: 'has-tooltip js-zen-enter', data: { container: 'body' } }) do
+ = _("Go full screen")
diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml
index 69ff477d415..94b7fe14721 100644
--- a/app/views/shared/issuable/_feed_buttons.html.haml
+++ b/app/views/shared/issuable/_feed_buttons.html.haml
@@ -1,8 +1,8 @@
- show_calendar_button = local_assigns.fetch(:show_calendar_button, true)
-= link_to safe_params.merge(rss_url_options), class: 'btn gl-button btn-default btn-icon has-tooltip', data: { container: 'body', testid: 'rss-feed-link' }, title: _('Subscribe to RSS feed') , 'aria-label': _('Subscribe to RSS feed') do
- = sprite_icon('rss')
+= render Pajamas::ButtonComponent.new(href: safe_params.merge(rss_url_options), icon: 'rss', button_options: { class: 'has-tooltip', 'aria-label': _('Subscribe to RSS feed'), data: { container: 'body', testid: 'rss-feed-link' } }) do
+ = _('Subscribe to RSS feed')
- if show_calendar_button
- = link_to safe_params.merge(calendar_url_options), class: 'btn gl-button btn-default btn-icon has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar'), 'aria-label': _('Subscribe to calendar') do
- = sprite_icon('calendar')
+ = render Pajamas::ButtonComponent.new(href: safe_params.merge(calendar_url_options), icon: 'calendar', button_options: { class: 'has-tooltip', 'aria-label': _('Subscribe to calendar'), data: { container: 'body' } }) do
+ = _('Subscribe to calendar')
diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml
index a5170b199e8..d342894e047 100644
--- a/app/views/shared/projects/_search_form.html.haml
+++ b/app/views/shared/projects/_search_form.html.haml
@@ -4,7 +4,7 @@
= form_tag filter_projects_path, method: :get, class: 'project-filter-form', data: { qa_selector: 'project_filter_form_container' }, id: 'project-filter-form' do |f|
= search_field_tag :name, params[:name],
placeholder: placeholder,
- class: "project-filter-form-field form-control #{form_field_classes}",
+ class: "project-filter-form-field form-control gl-w-full! gl-pl-7 #{form_field_classes}",
spellcheck: false,
id: 'project-filter-form-field',
autofocus: local_assigns[:autofocus]
diff --git a/config/application.rb b/config/application.rb
index a4607f914a6..0ed3e6fbd2d 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -255,6 +255,8 @@ module Gitlab
config.assets.precompile << "mailers/*.css"
config.assets.precompile << "page_bundles/_mixins_and_variables_and_functions.css"
config.assets.precompile << "page_bundles/admin/application_settings_metrics_and_profiling.css"
+ config.assets.precompile << "page_bundles/admin/geo_nodes.css"
+ config.assets.precompile << "page_bundles/admin/geo_replicable.css"
config.assets.precompile << "page_bundles/admin/jobs_index.css"
config.assets.precompile << "page_bundles/alert_management_details.css"
config.assets.precompile << "page_bundles/alert_management_settings.css"
diff --git a/config/feature_flags/development/search_page_vertical_nav.yml b/config/feature_flags/development/search_page_vertical_nav.yml
new file mode 100644
index 00000000000..d33c80e5f2f
--- /dev/null
+++ b/config/feature_flags/development/search_page_vertical_nav.yml
@@ -0,0 +1,8 @@
+---
+name: search_page_vertical_nav
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97784
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342621
+milestone: '15.5'
+type: development
+group: group::global search
+default_enabled: false
diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
index 19dd40cc62f..c38e690000c 100644
--- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
+++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
@@ -77,12 +77,6 @@ irb(#<Project>)> web_url
# => "https://gitlab-example/root/discard"
```
-## View all keys in cache
-
-```ruby
-Rails.cache.instance_variable_get(:@data).keys
-```
-
## Profile a page
```ruby
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1599f92c38e..1a9d65e105c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -26991,6 +26991,9 @@ msgid_plural "No worries, you can still use all the %{strong}%{plan_name}%{stron
msgstr[0] ""
msgstr[1] ""
+msgid "No wrap"
+msgstr ""
+
msgid "No. of commits"
msgstr ""
@@ -37672,6 +37675,9 @@ msgstr ""
msgid "Snowplow"
msgstr ""
+msgid "Soft wrap"
+msgstr ""
+
msgid "Solid"
msgstr ""
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index 3c774f8b269..0d10aed955a 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'Dashboard Issues filtering', :js do
it 'updates atom feed link' do
visit_issues(milestone_title: '', assignee_username: user.username)
- link = find('.nav-controls a[title="Subscribe to RSS feed"]')
+ link = find('[data-testid="rss-feed-link"]')
params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
diff --git a/spec/fixtures/markdown/markdown_golden_master_examples.yml b/spec/fixtures/markdown/markdown_golden_master_examples.yml
index 495d00026d7..1c10f4fb9e0 100644
--- a/spec/fixtures/markdown/markdown_golden_master_examples.yml
+++ b/spec/fixtures/markdown/markdown_golden_master_examples.yml
@@ -427,6 +427,14 @@
html: |-
<a class="no-attachment-icon" href="http://localhost:8080/png/U9nJK73CoKnELT2rKt3AJx9IS2mjoKZDAybCJYp9pCzJ24ejB4qjBk5I0Cagw09LWPLZKLTSa9zNdCe5L8bcO5u-K6MHGY8kWo7ARNHr2QY7MW00AeWxTG00" target="_blank" rel="noopener noreferrer" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,ICBBbGljZSAtPiBCb2I6IEF1dGhlbnRpY2F0aW9uIFJlcXVlc3QKICBCb2IgLS0+IEFsaWNlOiBBdXRoZW50aWNhdGlvbiBSZXNwb25zZQoKICBBbGljZSAtPiBCb2I6IEFub3RoZXIgYXV0aGVudGljYXRpb24gUmVxdWVzdAogIEFsaWNlIDwtLSBCb2I6IEFub3RoZXIgYXV0aGVudGljYXRpb24gUmVzcG9uc2UK"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" decoding="async" class="lazy" data-src="http://localhost:8080/png/U9nJK73CoKnELT2rKt3AJx9IS2mjoKZDAybCJYp9pCzJ24ejB4qjBk5I0Cagw09LWPLZKLTSa9zNdCe5L8bcO5u-K6MHGY8kWo7ARNHr2QY7MW00AeWxTG00"></a>
+- name: diagram_plantuml_unicode
+ markdown: |-
+ ```plantuml
+ A -> B : Text with norwegian characters: æøå
+ ```
+ html: |-
+ <a class="no-attachment-icon" href="http://localhost:8080/png/U9npLD2rKt1Ii588IQqeKIZFBCbGoCilAazDpqpCKqZEI2nAJ2v9BIgsKZYyxF2Emqkv07hO4WG0" target="_blank" rel="noopener noreferrer" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,QSAtPiBCIDogVGV4dCB3aXRoIG5vcndlZ2lhbiBjaGFyYWN0ZXJzOiDDpsO4w6UK"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" decoding="async" class="lazy" data-src="http://localhost:8080/png/U9npLD2rKt1Ii588IQqeKIZFBCbGoCilAazDpqpCKqZEI2nAJ2v9BIgsKZYyxF2Emqkv07hO4WG0"></a>
+
- name: div
markdown: |-
<div>plain text</div>
diff --git a/spec/frontend/__helpers__/dom_shims/index.js b/spec/frontend/__helpers__/dom_shims/index.js
index 742d55196b4..3b41e2ca2a7 100644
--- a/spec/frontend/__helpers__/dom_shims/index.js
+++ b/spec/frontend/__helpers__/dom_shims/index.js
@@ -11,3 +11,4 @@ import './window_scroll_to';
import './scroll_by';
import './size_properties';
import './image_element_properties';
+import './text_encoder';
diff --git a/spec/frontend/__helpers__/dom_shims/text_encoder.js b/spec/frontend/__helpers__/dom_shims/text_encoder.js
new file mode 100644
index 00000000000..d3d5221a003
--- /dev/null
+++ b/spec/frontend/__helpers__/dom_shims/text_encoder.js
@@ -0,0 +1,4 @@
+import { TextEncoder, TextDecoder } from 'util';
+
+global.TextEncoder = TextEncoder;
+global.TextDecoder = TextDecoder;
diff --git a/spec/frontend/jira_connect/subscriptions/pkce_spec.js b/spec/frontend/jira_connect/subscriptions/pkce_spec.js
index 4ee88059b7a..671922c36d8 100644
--- a/spec/frontend/jira_connect/subscriptions/pkce_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pkce_spec.js
@@ -1,11 +1,7 @@
import crypto from 'crypto';
-import { TextEncoder, TextDecoder } from 'util';
import { createCodeVerifier, createCodeChallenge } from '~/jira_connect/subscriptions/pkce';
-global.TextEncoder = TextEncoder;
-global.TextDecoder = TextDecoder;
-
describe('pkce', () => {
beforeAll(() => {
Object.defineProperty(global.self, 'crypto', {
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 49a160c9f23..f2572ca0ad2 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -386,4 +386,16 @@ describe('text_utility', () => {
expect(textUtils.limitedCounterWithDelimiter(120)).toBe(120);
});
});
+
+ describe('base64EncodeUnicode', () => {
+ it('encodes unicode characters', () => {
+ expect(textUtils.base64EncodeUnicode('😀')).toBe('8J+YgA==');
+ });
+ });
+
+ describe('base64DecodeUnicode', () => {
+ it('decodes unicode characters', () => {
+ expect(textUtils.base64DecodeUnicode('8J+YgA==')).toBe('😀');
+ });
+ });
});
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index ad0705e4fbf..f3255a0c1d4 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -848,4 +848,289 @@ RSpec.describe SearchHelper do
end
end
end
+
+ describe '.search_navigation' do
+ using RSpec::Parameterized::TableSyntax
+ let(:user) { build(:user) }
+ let(:project) { build(:project) }
+
+ before do
+ stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
+ allow(self).to receive(:current_user).and_return(user)
+ allow(self).to receive(:can?).and_return(true)
+ allow(self).to receive(:project_search_tabs?).and_return(false)
+ allow(self).to receive(:feature_flag_tab_enabled?).and_return(false)
+ end
+
+ context 'projects' do
+ where(:global_project, :condition) do
+ nil | true
+ ref(:project) | false
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ @project = global_project
+
+ expect(search_navigation[:projects][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'code' do
+ where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :project_search_tabs, :condition) do
+ false | false | false | false
+ true | true | true | true
+ true | false | false | true
+ false | true | false | true
+ false | false | true | true
+ true | false | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
+ allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_code_tab).and_return(feature_flag_tab_enabled)
+ allow(self).to receive(:project_search_tabs?).with(:blobs).and_return(project_search_tabs)
+
+ expect(search_navigation[:blobs][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'epics' do
+ where(:global_project, :show_epics, :condition) do
+ nil | false | false
+ ref(:project) | true | false
+ ref(:project) | false | false
+ nil | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ @project = global_project
+ allow(search_service).to receive(:show_epics?).and_return(show_epics)
+
+ expect(search_navigation[:epics][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'issues' do
+ where(:feature_flag_tab_enabled, :project_search_tabs, :condition) do
+ false | false | false
+ true | true | true
+ true | false | true
+ false | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_issues_tab).and_return(feature_flag_tab_enabled)
+ allow(self).to receive(:project_search_tabs?).with(:issues).and_return(project_search_tabs)
+
+ expect(search_navigation[:issues][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'merge requests' do
+ where(:feature_flag_tab_enabled, :project_search_tabs, :condition) do
+ false | false | false
+ true | true | true
+ true | false | true
+ false | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_merge_requests_tab).and_return(feature_flag_tab_enabled)
+ allow(self).to receive(:project_search_tabs?).with(:merge_requests).and_return(project_search_tabs)
+
+ expect(search_navigation[:merge_requests][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'wiki' do
+ where(:project_search_tabs, :show_elasticsearch_tabs, :condition) do
+ false | false | false
+ true | true | true
+ true | false | true
+ false | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
+ allow(self).to receive(:project_search_tabs?).with(:wiki).and_return(project_search_tabs)
+
+ expect(search_navigation[:wiki_blobs][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'commits' do
+ where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :project_search_tabs, :condition) do
+ false | false | false | false
+ true | true | true | true
+ true | false | false | false
+ false | true | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
+ allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_commits_tab).and_return(feature_flag_tab_enabled)
+ allow(self).to receive(:project_search_tabs?).with(:commits).and_return(project_search_tabs)
+
+ expect(search_navigation[:commits][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'comments' do
+ where(:show_elasticsearch_tabs, :project_search_tabs, :condition) do
+ true | true | true
+ false | false | false
+ true | false | true
+ false | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
+ allow(self).to receive(:project_search_tabs?).with(:notes).and_return(project_search_tabs)
+
+ expect(search_navigation[:notes][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'milestones' do
+ where(:global_project, :project_search_tabs, :condition) do
+ ref(:project) | true | true
+ nil | false | true
+ ref(:project) | false | false
+ nil | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ @project = global_project
+ allow(self).to receive(:project_search_tabs?).with(:milestones).and_return(project_search_tabs)
+
+ expect(search_navigation[:milestones][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'users' do
+ where(:show_user_search_tab, :condition) do
+ true | true
+ false | false
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ allow(self).to receive(:show_user_search_tab?).and_return(show_user_search_tab)
+
+ expect(search_navigation[:users][:condition]).to eq(condition)
+ end
+ end
+ end
+
+ context 'snippet_titles' do
+ where(:global_project, :global_show_snippets, :condition) do
+ ref(:project) | true | false
+ nil | false | false
+ ref(:project) | false | false
+ nil | true | true
+ end
+
+ with_them do
+ it 'data item condition is set correctly' do
+ @show_snippets = global_show_snippets
+ @project = global_project
+
+ expect(search_navigation[:snippet_titles][:condition]).to eq(condition)
+ end
+ end
+ end
+ end
+
+ describe '.search_navigation_json' do
+ using RSpec::Parameterized::TableSyntax
+ context 'data' do
+ example_data_1 = {
+ projects: { label: _("Projects"), condition: true },
+ blobs: { label: _("Code"), condition: false }
+ }
+
+ example_data_2 = {
+ projects: { label: _("Projects"), condition: false },
+ blobs: { label: _("Code"), condition: false }
+ }
+
+ example_data_3 = {
+ projects: { label: _("Projects"), condition: true },
+ blobs: { label: _("Code"), condition: true },
+ epics: { label: _("Epics"), condition: true }
+ }
+
+ where(:data, :matcher) do
+ example_data_1 | -> { include("projects") }
+ example_data_2 | -> { eq("{}") }
+ example_data_3 | -> { include("projects", "blobs", "epics") }
+ end
+
+ with_them do
+ it 'converts correctly' do
+ allow(self).to receive(:search_navigation).with(no_args).and_return(data)
+ expect(search_navigation_json).to instance_exec(&matcher)
+ end
+ end
+ end
+ end
+
+ describe '.search_filter_link_json' do
+ using RSpec::Parameterized::TableSyntax
+
+ context 'data' do
+ where(:scope, :label, :data, :search, :active_scope) do
+ "projects" | "Projects" | { qa_selector: 'projects_tab' } | nil | "projects"
+ "snippet_titles" | "Titles and Descriptions" | nil | { snippets: "test" } | "code"
+ "projects" | "Projects" | { qa_selector: 'projects_tab' } | nil | "issue"
+ "snippet_titles" | "Titles and Descriptions" | nil | { snippets: "test" } | "snippet_titles"
+ end
+
+ with_them do
+ it 'converts correctly' do
+ @timeout = false
+ @scope = active_scope
+ @search_results = double
+ dummy_count = 1000
+ allow(self).to receive(:search_path).with(any_args).and_return("link test")
+
+ allow(@search_results).to receive(:formatted_count).with(scope).and_return(dummy_count)
+ allow(self).to receive(:search_count_path).with(any_args).and_return("test count link")
+
+ current_scope = scope == active_scope
+
+ expected = {
+ label: label,
+ scope: scope,
+ data: data,
+ link: "link test",
+ active: current_scope
+ }
+
+ expected[:count] = dummy_count if current_scope
+ expected[:count_link] = "test count link" unless current_scope
+
+ expect(search_filter_link_json(scope, label, data, search)).to eq(expected)
+ end
+ end
+ end
+ end
end