diff options
Diffstat (limited to 'app/helpers')
52 files changed, 450 insertions, 235 deletions
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 340f3d45365..c78563a9a5f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -124,7 +124,7 @@ module ApplicationHelper end def simple_sanitize(str) - sanitize(str, tags: %w(a span)) + sanitize(str, tags: %w[a span]) end def body_data @@ -187,14 +187,14 @@ module ApplicationHelper css_classes << html_class unless html_class.blank? content_tag :time, l(time, format: "%b %d, %Y"), - class: css_classes.join(' '), - title: l(time.to_time.in_time_zone, format: :timeago_tooltip), - datetime: time.to_time.getutc.iso8601, - data: { - toggle: 'tooltip', - placement: placement, - container: 'body' - } + class: css_classes.join(' '), + title: l(time.to_time.in_time_zone, format: :timeago_tooltip), + datetime: time.to_time.getutc.iso8601, + data: { + toggle: 'tooltip', + placement: placement, + container: 'body' + } end def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', exclude_author: false) @@ -234,11 +234,11 @@ module ApplicationHelper end def promo_url - 'https://' + promo_host + "https://#{promo_host}" end def support_url - Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/' + Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || "#{promo_url}/getting-help/" end def instance_review_permitted? @@ -279,7 +279,19 @@ module ApplicationHelper end def stylesheet_link_tag_defer(path) - stylesheet_link_tag(path, media: "print", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil) + if startup_css_enabled? + stylesheet_link_tag(path, media: "print", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil) + else + stylesheet_link_tag(path, crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil) + end + end + + def startup_css_enabled? + !params.has_key?(:no_startup_css) + end + + def use_new_fonts? + Feature.enabled?(:new_fonts, current_user) || request.params.has_key?(:new_fonts) end def outdated_browser? diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 7f13f609353..2b2ac262848 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -216,6 +216,7 @@ module ApplicationSettingsHelper :default_branch_protection, :default_ci_config_path, :default_group_visibility, + :default_preferred_language, :default_project_creation, :default_project_visibility, :default_projects_limit, @@ -287,6 +288,7 @@ module ApplicationSettingsHelper :max_import_size, :max_pages_size, :max_pages_custom_domains_per_project, + :max_terraform_state_size_bytes, :max_yaml_size_bytes, :max_yaml_depth, :metrics_method_call_threshold, @@ -318,7 +320,6 @@ module ApplicationSettingsHelper :require_two_factor_authentication, :restricted_visibility_levels, :rsa_key_restriction, - :send_user_confirmation_email, :session_expire_delay, :shared_runners_enabled, :shared_runners_text, @@ -445,7 +446,8 @@ module ApplicationSettingsHelper :project_runner_token_expiration_interval, :pipeline_limit_per_project_user_sha, :invitation_flow_enforcement, - :can_create_group + :can_create_group, + :bulk_import_enabled ].tap do |settings| next if Gitlab.com? @@ -545,7 +547,6 @@ module ApplicationSettingsHelper settings_path: general_admin_application_settings_path(anchor: 'js-signup-settings'), signup_enabled: @application_setting[:signup_enabled].to_s, require_admin_approval_after_user_signup: @application_setting[:require_admin_approval_after_user_signup].to_s, - send_user_confirmation_email: @application_setting[:send_user_confirmation_email].to_s, email_confirmation_setting: @application_setting[:email_confirmation_setting].to_s, minimum_password_length: @application_setting[:minimum_password_length], minimum_password_length_min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH, diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 07152133402..c41b5923d13 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -70,11 +70,9 @@ module AuthHelper end def form_based_provider_with_highest_priority - @form_based_provider_with_highest_priority ||= begin - form_based_provider_priority.each do |provider_regexp| - highest_priority = form_based_providers.find { |provider| provider.match?(provider_regexp) } - break highest_priority unless highest_priority.nil? - end + @form_based_provider_with_highest_priority ||= form_based_provider_priority.each do |provider_regexp| + highest_priority = form_based_providers.find { |provider| provider.match?(provider_regexp) } + break highest_priority unless highest_priority.nil? end end diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index 798bb7b64a4..0fac2cb5fc5 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -91,7 +91,7 @@ module AvatarsHelper title: user_name } - tag(:img, image_options) + tag.img(**image_options) end def user_avatar(options = {}) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index f08c1a2ff0a..281d5c923d0 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -113,7 +113,7 @@ module BlobHelper end def parent_dir_raw_path - blob_raw_path.rpartition("/").first + "/" + "#{blob_raw_path.rpartition('/').first}/" end # SVGs can contain malicious JavaScript; only include whitelisted @@ -295,7 +295,7 @@ module BlobHelper end def edit_link_tag(link_text, edit_path, common_classes) - link_to link_text, edit_path, class: "#{common_classes}" + link_to link_text, edit_path, class: common_classes end def edit_button_tag(blob, common_classes, text, edit_path, project, ref) diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb index 7b8290ac9ef..424e5920fed 100644 --- a/app/helpers/ci/jobs_helper.rb +++ b/app/helpers/ci/jobs_helper.rb @@ -39,7 +39,7 @@ module Ci def job_statuses statuses = Ci::HasStatus::AVAILABLE_STATUSES - statuses.to_h { |status| [status, status.upcase] } + statuses.index_with(&:upcase) end end end diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index 0de84c0d61f..8df30ee1f0d 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -96,8 +96,8 @@ module Ci def toggle_shared_runners_settings_data(project) { - is_enabled: "#{project.shared_runners_enabled?}", - is_disabled_and_unoverridable: "#{project.group&.shared_runners_setting == Namespace::SR_DISABLED_AND_UNOVERRIDABLE}", + is_enabled: project.shared_runners_enabled?.to_s, + is_disabled_and_unoverridable: (project.group&.shared_runners_setting == Namespace::SR_DISABLED_AND_UNOVERRIDABLE).to_s, update_path: toggle_shared_runners_project_runners_path(project) } end diff --git a/app/helpers/ci/secure_files_helper.rb b/app/helpers/ci/secure_files_helper.rb index 30b2e12ac3b..fca89ddab1e 100644 --- a/app/helpers/ci/secure_files_helper.rb +++ b/app/helpers/ci/secure_files_helper.rb @@ -4,7 +4,7 @@ module Ci def show_secure_files_setting(project, user) return false if user.nil? - Feature.enabled?(:ci_secure_files, project) && user.can?(:read_secure_files, project) + user.can?(:read_secure_files, project) end end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 4493bc2bc6d..53781364af7 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -124,7 +124,7 @@ module CommitsHelper new_project_tag_path: new_project_tag_path(project, ref: commit), email_patches_path: project_commit_path(project, commit, format: :patch), plain_diff_path: project_commit_path(project, commit, format: :diff), - can_revert: "#{can_collaborate && !commit.has_been_reverted?(current_user)}", + can_revert: (can_collaborate && !commit.has_been_reverted?(current_user)).to_s, can_cherry_pick: can_collaborate.to_s, can_tag: can?(current_user, :push_code, project).to_s, can_email_patches: (commit.parents.length < 2).to_s diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index e05adc5cd0e..e0a1697cfa9 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -76,13 +76,11 @@ module DiffHelper def diff_line_content(line) if line.blank? " ".html_safe - else + elsif line.start_with?('+', '-', ' ') # `sub` and substring-ing would destroy HTML-safeness of `line` - if line.start_with?('+', '-', ' ') - line[1, line.length] - else - line - end + line[1, line.length] + else + line end end @@ -227,7 +225,7 @@ module DiffHelper end def conflicts(allow_tree_conflicts: false) - return unless merge_request.cannot_be_merged? + return unless merge_request.cannot_be_merged? && merge_request.source_branch_exists? && merge_request.target_branch_exists? conflicts_service = MergeRequests::Conflicts::ListService.new(merge_request, allow_tree_conflicts: allow_tree_conflicts) # rubocop:disable CodeReuse/ServiceClass diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 62e66b9a3ea..427cbe18fbf 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -17,8 +17,8 @@ module DropdownsHelper end content_tag_options = { class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}" } - content_tag_options[:data] = options[:dropdown_qa_selector] ? { qa_selector: "#{options[:dropdown_qa_selector]}" } : {} - content_tag_options[:data][:testid] = "#{options[:dropdown_testid]}" if options[:dropdown_testid] + content_tag_options[:data] = options[:dropdown_qa_selector] ? { qa_selector: (options[:dropdown_qa_selector]).to_s } : {} + content_tag_options[:data][:testid] = (options[:dropdown_testid]).to_s if options[:dropdown_testid] dropdown_output << content_tag(:div, content_tag_options) do output = [] @@ -86,7 +86,7 @@ module DropdownsHelper title_output = [] if has_back - title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back " + margin_class, aria: { label: "Go back" }, type: "button") do + title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back #{margin_class}", aria: { label: "Go back" }, type: "button") do sprite_icon('arrow-left') end end @@ -94,7 +94,7 @@ module DropdownsHelper title_output << content_tag(:span, title, class: margin_class) if has_close - title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close " + margin_class, aria: { label: "Close" }, type: "button") do + title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close #{margin_class}", aria: { label: "Close" }, type: "button") do sprite_icon('close', size: 16, css_class: 'dropdown-menu-close-icon') end end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 54733fa9101..cad39854c0e 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -139,7 +139,7 @@ module EmailsHelper max_domain_length = list_id_max_length - Gitlab.config.gitlab.host.length - project.id.to_s.length - 2 if max_domain_length < 3 - return project.id.to_s + "..." + Gitlab.config.gitlab.host + return "#{project.id}...#{Gitlab.config.gitlab.host}" end if project_path_as_domain.length > max_domain_length @@ -151,7 +151,7 @@ module EmailsHelper project_path_as_domain = project_path_as_domain.slice(0, last_dot_index).concat("..") end - project.id.to_s + "." + project_path_as_domain + "." + Gitlab.config.gitlab.host + "#{project.id}.#{project_path_as_domain}.#{Gitlab.config.gitlab.host}" end def html_header_message diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index b6997b6fb70..0e64a98c9da 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -72,6 +72,7 @@ module EnvironmentHelper { name: environment.name, id: environment.id, + project_full_path: project.full_path, external_url: environment.external_url, can_update_environment: can?(current_user, :update_environment, environment), can_destroy_environment: can_destroy_environment?(environment), diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index 333237db6a4..5bf4fa2ffcc 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -67,7 +67,7 @@ module EnvironmentsHelper 'external_dashboard_url' => project.metrics_setting_external_dashboard_url, 'custom_metrics_path' => project_prometheus_metrics_path(project), 'validate_query_path' => validate_query_project_prometheus_metrics_path(project), - 'custom_metrics_available' => "#{custom_metrics_available?(project)}", + 'custom_metrics_available' => custom_metrics_available?(project).to_s, 'dashboard_timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase } end @@ -78,8 +78,8 @@ module EnvironmentsHelper { 'metrics_dashboard_base_path' => metrics_dashboard_base_path(environment, project), 'current_environment_name' => environment.name, - 'has_metrics' => "#{environment.has_metrics?}", - 'environment_state' => "#{environment.state}" + 'has_metrics' => environment.has_metrics?.to_s, + 'environment_state' => environment.state.to_s } end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 087e4838ed9..bef2da495b0 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -92,7 +92,7 @@ module EventsHelper content_tag :li, class: active do link_to request.path, link_opts do - content_tag(:span, ' ' + text) + content_tag(:span, " #{text}") end end end diff --git a/app/helpers/groups/observability_helper.rb b/app/helpers/groups/observability_helper.rb index 6fb6acce386..26caac4ce7f 100644 --- a/app/helpers/groups/observability_helper.rb +++ b/app/helpers/groups/observability_helper.rb @@ -5,15 +5,15 @@ module Groups ACTION_TO_PATH = { 'dashboards' => { path: '/', - title: -> { s_('Dashboards') } + title: -> { _('Dashboards') } }, 'manage' => { path: '/dashboards', - title: -> { s_('Manage Dashboards') } + title: -> { _('Manage Dashboards') } }, 'explore' => { path: '/explore', - title: -> { s_('Explore') } + title: -> { _('Explore') } } }.freeze @@ -22,7 +22,7 @@ module Groups # When running Observability UI in standalone mode (i.e. not backed by Observability Backend) # the group-id is not required. This is mostly used for local dev - base_url = ENV['STANDALONE_OBSERVABILITY_UI'] == 'true' ? observability_url : "#{observability_url}/#{group.id}" + base_url = ENV['STANDALONE_OBSERVABILITY_UI'] == 'true' ? observability_url : "#{observability_url}/-/#{group.id}" sanitized_path = if params[:observability_path] && sanitize(params[:observability_path]) != '' CGI.unescapeHTML(sanitize(params[:observability_path])) diff --git a/app/helpers/groups/settings_helper.rb b/app/helpers/groups/settings_helper.rb index 1b391680996..38300043dd7 100644 --- a/app/helpers/groups/settings_helper.rb +++ b/app/helpers/groups/settings_helper.rb @@ -9,7 +9,7 @@ module Groups remove_form_id: remove_form_id, button_text: _('Remove group'), button_testid: 'remove-group-button', - disabled: group.paid?.to_s, + disabled: group.prevent_delete?.to_s, confirm_danger_message: remove_group_message(group), phrase: group.full_path } diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb index 921e30edbaa..63544e28a0e 100644 --- a/app/helpers/hooks_helper.rb +++ b/app/helpers/hooks_helper.rb @@ -10,7 +10,7 @@ module HooksHelper def link_to_test_hook(hook, trigger) path = test_hook_path(hook, trigger) - trigger_human_name = trigger.to_s.tr('_', ' ').camelize + trigger_human_name = integration_webhook_event_human_name(trigger) link_to path, rel: 'nofollow', method: :post do content_tag(:span, trigger_human_name) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index c81041c2d9c..021b47ceab2 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -38,7 +38,7 @@ module IconsHelper css_classes = [] css_classes << "s#{size}" if size - css_classes << "#{css_class}" unless css_class.blank? + css_classes << css_class.to_s unless css_class.blank? content_tag( :svg, diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 34f4749c42a..0e81cea8ac7 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -7,7 +7,10 @@ module IdeHelper 'use-new-web-ide' => use_new_web_ide?.to_s, 'new-web-ide-help-page-path' => help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'), 'user-preferences-path' => profile_preferences_path, - 'branch-name' => @branch + 'branch-name' => @branch, + 'file-path' => @path, + 'fork-info' => @fork_info&.to_json, + 'merge-request' => @merge_request }.merge(use_new_web_ide? ? new_ide_data : legacy_ide_data) end @@ -24,7 +27,9 @@ module IdeHelper def new_ide_data { 'project-path' => @project&.path_with_namespace, - 'csp-nonce' => content_security_policy_nonce + 'csp-nonce' => content_security_policy_nonce, + # We will replace these placeholders in the FE + 'ide-remote-path' => ide_remote_path(remote_host: ':remote_host', remote_path: ':remote_path') } end @@ -42,9 +47,6 @@ module IdeHelper 'render-whitespace-in-code': current_user.render_whitespace_in_code.to_s, 'codesandbox-bundler-url': Gitlab::CurrentSettings.web_ide_clientside_preview_bundler_url, 'default-branch' => @project && @project.default_branch, - 'file-path' => @path, - 'merge-request' => @merge_request, - 'fork-info' => @fork_info&.to_json, 'project' => convert_to_project_entity_json(@project), 'enable-environments-guidance' => enable_environments_guidance?.to_s, 'preview-markdown-path' => @project && preview_markdown_path(@project), diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index abfa55cff24..0650af33e37 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -185,6 +185,29 @@ module IntegrationsHelper target_type_i18n_map[target_type] || target_type end + def integration_webhook_event_human_name(event) + event_i18n_map = { + repository_update_events: _('Repository update events'), + push_events: _('Push events'), + tag_push_events: s_('Webhooks|Tag push events'), + note_events: _('Comments'), + confidential_note_events: s_('Webhooks|Confidential comments'), + issues_events: s_('Webhooks|Issues events'), + confidential_issues_events: s_('Webhooks|Confidential issues events'), + subgroup_events: s_('Webhooks|Subgroup events'), + member_events: s_('Webhooks|Member events'), + merge_requests_events: s_('Webhooks|Merge request events'), + job_events: s_('Webhooks|Job events'), + pipeline_events: s_('Webhooks|Pipeline events'), + wiki_page_events: s_('Webhooks|Wiki page events'), + deployment_events: s_('Webhooks|Deployment events'), + feature_flag_events: s_('Webhooks|Feature flag events'), + releases_events: s_('Webhooks|Releases events') + } + + event_i18n_map[event] || event.to_s.humanize + end + extend self private diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index 5d537767eaf..6fad1346426 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -20,6 +20,7 @@ module InviteMembersHelper end end + # Overridden in EE def common_invite_group_modal_data(source, member_class, is_project) { id: source.id, @@ -29,7 +30,8 @@ module InviteMembersHelper invalid_groups: source.related_group_ids, help_link: help_page_url('user/permissions'), is_project: is_project, - access_levels: member_class.permissible_access_level_roles(current_user, source).to_json + access_levels: member_class.permissible_access_level_roles(current_user, source).to_json, + full_path: source.full_path }.merge(group_select_data(source)) end @@ -39,7 +41,8 @@ module InviteMembersHelper id: source.id, root_id: source.root_ancestor&.id, name: source.name, - default_access_level: Gitlab::Access::GUEST + default_access_level: Gitlab::Access::GUEST, + full_path: source.full_path } if show_invite_members_for_task?(source) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index fd181109a94..2b21d8c51e6 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -138,15 +138,15 @@ module IssuablesHelper def issuable_meta_author_status(author) return "" unless author&.status&.customized? && status = user_status(author) - "#{status}".html_safe + status.to_s.html_safe end def issuable_meta(issuable, project) output = [] if issuable.respond_to?(:work_item_type) && WorkItems::Type::WI_TYPES_WITH_CREATED_HEADER.include?(issuable.work_item_type.base_type) - output << content_tag(:span, sprite_icon("#{issuable.work_item_type.icon_name}", css_class: 'gl-icon gl-vertical-align-middle gl-text-gray-500'), class: 'gl-mr-2', aria: { hidden: 'true' }) - output << content_tag(:span, s_('IssuableStatus|%{wi_type} created %{created_at} by ').html_safe % { wi_type: issuable.issue_type.capitalize, created_at: time_ago_with_tooltip(issuable.created_at) }, class: 'gl-mr-2') + output << content_tag(:span, sprite_icon(issuable.work_item_type.icon_name.to_s, css_class: 'gl-icon gl-vertical-align-middle gl-text-gray-500'), class: 'gl-mr-2', aria: { hidden: 'true' }) + output << content_tag(:span, s_('IssuableStatus|%{wi_type} created %{created_at} by ').html_safe % { wi_type: IntegrationsHelper.integration_issue_type(issuable.issue_type), created_at: time_ago_with_tooltip(issuable.created_at) }, class: 'gl-mr-2') else output << content_tag(:span, s_('IssuableStatus|Created %{created_at} by').html_safe % { created_at: time_ago_with_tooltip(issuable.created_at) }, class: 'gl-mr-2') end @@ -207,7 +207,14 @@ module IssuablesHelper def assigned_issuables_count(issuable_type) case issuable_type when :issues - current_user.assigned_open_issues_count + if Feature.enabled?(:limit_assigned_issues_count) + ::Users::AssignedIssuesCountService.new( + current_user: current_user, + max_limit: User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT + ).count + else + current_user.assigned_open_issues_count + end when :merge_requests current_user.assigned_open_merge_requests_count else @@ -215,6 +222,16 @@ module IssuablesHelper end end + def assigned_open_issues_count_text + count = assigned_issuables_count(:issues) + + if Feature.enabled?(:limit_assigned_issues_count) && count > User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT - 1 + "#{count - 1}+" + else + count.to_s + end + end + def issuable_reference(issuable) @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project) end @@ -348,12 +365,10 @@ module IssuablesHelper else [_("Closed"), "merge-request-close"] end + elsif issuable.open? + [_("Open"), "issues"] else - if issuable.open? - [_("Open"), "issues"] - else - [_("Closed"), "issue-closed"] - end + [_("Closed"), "issue-closed"] end end @@ -414,6 +429,7 @@ module IssuablesHelper id: issuable[:id], severity: issuable[:severity], timeTrackingLimitToHours: Gitlab::CurrentSettings.time_tracking_limit_to_hours, + canCreateTimelogs: issuable.dig(:current_user, :can_create_timelogs), createNoteEmail: issuable[:create_note_email], issuableType: issuable[:type] } diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 932a50d9451..1d68dccc741 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -256,6 +256,18 @@ module IssuesHelper ) end + def dashboard_issues_list_data(current_user) + { + calendar_path: url_for(safe_params.merge(calendar_url_options)), + empty_state_svg_path: image_path('illustrations/issue-dashboard_results-without-filter.svg'), + initial_sort: current_user&.user_preference&.issues_sort, + is_public_visibility_restricted: + Gitlab::CurrentSettings.restricted_visibility_levels&.include?(Gitlab::VisibilityLevel::PUBLIC).to_s, + is_signed_in: current_user.present?.to_s, + rss_path: url_for(safe_params.merge(rss_url_options)) + } + end + def issues_form_data(project) { new_issue_path: new_project_issue_path(project) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 0123eb68c9a..8c069bc828b 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -159,7 +159,7 @@ module LabelsHelper end def label_subscription_toggle_button_text(label, project = nil) - label.subscribed?(current_user, project) ? 'Unsubscribe' : 'Subscribe' + label.subscribed?(current_user, project) ? _('Unsubscribe') : _('Subscribe') end def create_label_title(subject) @@ -219,8 +219,8 @@ module LabelsHelper }.merge(opts) end - def issuable_types - ['issues', 'merge requests'] + def labels_function_introduction + _('Labels can be applied to issues and merge requests. Group labels are available for any project within the group.') end def show_labels_full_path?(project, group) diff --git a/app/helpers/listbox_helper.rb b/app/helpers/listbox_helper.rb index 16caf862c7b..0aaeb39c82d 100644 --- a/app/helpers/listbox_helper.rb +++ b/app/helpers/listbox_helper.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module ListboxHelper - DROPDOWN_CONTAINER_CLASSES = %w[dropdown b-dropdown gl-new-dropdown btn-group js-redirect-listbox].freeze + DROPDOWN_CONTAINER_CLASSES = %w[dropdown b-dropdown gl-dropdown btn-group js-redirect-listbox].freeze DROPDOWN_BUTTON_CLASSES = %w[btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle].freeze - DROPDOWN_INNER_CLASS = 'gl-new-dropdown-button-text' + DROPDOWN_INNER_CLASS = 'gl-dropdown-button-text' DROPDOWN_ICON_CLASS = 'gl-button-icon dropdown-chevron gl-icon' # Creates a listbox component with redirect behavior. diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index 9baea43b77d..ed9129ff78b 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -59,12 +59,20 @@ module MarkupHelper # as Markdown. HTML tags in the parsed output are not counted toward the # +max_chars+ limit. If the length limit falls within a tag's contents, then # the tag contents are truncated without removing the closing tag. - def first_line_in_markdown(object, attribute, max_chars = nil, options = {}) + def first_line_in_markdown(object, attribute, max_chars = nil, is_todo: false, **options) md = markdown_field(object, attribute, options.merge(post_process: false)) return unless md.present? + includes_code = false + tags = %w(a gl-emoji b strong i em pre code p span) - tags << 'img' if options[:allow_images] + + if is_todo + fragment = Nokogiri::HTML.fragment(md) + includes_code = fragment.css('code').any? + + md = fragment + end context = markdown_field_render_context(object, attribute, options) context.reverse_merge!(truncate_visible_max_chars: max_chars || md.length) @@ -77,12 +85,19 @@ module MarkupHelper %w( style data-src data-name data-unicode-version data-html data-reference-type data-project-path data-iid data-mr-title + data-user ) ) + # Extra span with relative positioning relative due to system font being behind + # background color when username is first word of mention + if is_todo && !includes_code + text = "<span class=\"gl-relative\">\"</span>#{text}<span class=\"gl-relative\">\"</span>" + end + # since <img> tags are stripped, this can leave empty <a> tags hanging around # (as our markdown wraps images in links) - options[:allow_images] ? text : strip_empty_link_tags(text).html_safe + strip_empty_link_tags(text).html_safe end def markdown(text, context = {}) diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index f1f5f941edd..29f94adcc78 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -14,19 +14,17 @@ module MembersHelper else "deny #{member.user.name}'s request to join" end + elsif member.user + "remove #{member.user.name} from" else - if member.user - "remove #{member.user.name} from" - else - e = RuntimeError.new("Data integrity error: no associated user for member ID #{member.id}") - Gitlab::ErrorTracking.track_exception(e, - member_id: member.id, - invite_email: member.invite_email, - invite_accepted_at: member.invite_accepted_at, - source_id: member.source_id, - source_type: member.source_type) - "remove this orphaned member from" - end + e = RuntimeError.new("Data integrity error: no associated user for member ID #{member.id}") + Gitlab::ErrorTracking.track_exception(e, + member_id: member.id, + invite_email: member.invite_email, + invite_accepted_at: member.invite_accepted_at, + source_id: member.source_id, + source_type: member.source_type) + "remove this orphaned member from" end "#{text} #{action} the #{member.source.human_name} #{source_text(member)}?" diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index 751900f4593..bd4d661ab49 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -101,8 +101,7 @@ module Nav builder.add_primary_menu_item_with_shortcut( header: top_nav_localized_headers[:switch_to], active: nav == 'project' || active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]), - css_class: 'qa-projects-dropdown', - data: { track_label: "projects_dropdown", track_action: "click_dropdown" }, + data: { track_label: "projects_dropdown", track_action: "click_dropdown", qa_selector: "projects_dropdown" }, view: PROJECTS_VIEW, shortcut_href: dashboard_projects_path, **projects_menu_item_attrs @@ -116,8 +115,7 @@ module Nav builder.add_primary_menu_item_with_shortcut( header: top_nav_localized_headers[:switch_to], active: nav == 'group' || active_nav_link?(path: %w[dashboard/groups explore/groups]), - css_class: 'qa-groups-dropdown', - data: { track_label: "groups_dropdown", track_action: "click_dropdown" }, + data: { track_label: "groups_dropdown", track_action: "click_dropdown", qa_selector: "groups_dropdown" }, view: GROUPS_VIEW, shortcut_href: dashboard_groups_path, **groups_menu_item_attrs @@ -133,7 +131,7 @@ module Nav href: dashboard_milestones_path, active: active_nav_link?(controller: 'dashboard/milestones'), icon: 'clock', - data: { qa_selector: 'milestones_link', **menu_data_tracking_attrs('milestones') }, + data: { **menu_data_tracking_attrs('milestones') }, shortcut_class: 'dashboard-shortcuts-milestones' ) end @@ -156,7 +154,7 @@ module Nav href: activity_dashboard_path, active: active_nav_link?(path: 'dashboard#activity'), icon: 'history', - data: { qa_selector: 'activity_link', **menu_data_tracking_attrs('activity') }, + data: { **menu_data_tracking_attrs('activity') }, shortcut_class: 'dashboard-shortcuts-activity' ) end @@ -173,9 +171,8 @@ module Nav title: title, active: active_nav_link?(controller: 'admin/dashboard'), icon: 'admin', - css_class: 'qa-admin-area-link', href: admin_root_path, - data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } + data: { qa_selector: 'admin_area_link', **menu_data_tracking_attrs(title) } ) end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 0cf2c5cea4c..bf3b132e33a 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -21,11 +21,11 @@ module NavHelper def page_gutter_class moved_sidebar_enabled = current_controller?('merge_requests') && moved_mr_sidebar_enabled? - if page_has_markdown? && !current_controller?('conflicts') + if (page_has_markdown? || current_path?('projects/merge_requests#diffs')) && !current_controller?('conflicts') if cookies[:collapsed_gutter] == 'true' - ["page-gutter", "#{'right-sidebar-collapsed' unless moved_sidebar_enabled}"] + ["page-gutter", ('right-sidebar-collapsed' unless moved_sidebar_enabled).to_s] else - ["page-gutter", "#{'right-sidebar-expanded' unless moved_sidebar_enabled}"] + ["page-gutter", ('right-sidebar-expanded' unless moved_sidebar_enabled).to_s] end elsif current_path?('jobs#show') %w[page-gutter build-sidebar right-sidebar-expanded] diff --git a/app/helpers/numbers_helper.rb b/app/helpers/numbers_helper.rb index 38d3f90dd55..7184a9c075c 100644 --- a/app/helpers/numbers_helper.rb +++ b/app/helpers/numbers_helper.rb @@ -6,7 +6,7 @@ module NumbersHelper count = resource.page.total_count_with_limit(:all, limit: limit) if count > limit - number_with_delimiter(count - 1, options) + '+' + "#{number_with_delimiter(count - 1, options)}+" else number_with_delimiter(count, options) end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index c0665463706..6f7b2877100 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -80,8 +80,8 @@ module PageLayoutHelper tags = [] page_card_attributes.each_with_index do |pair, i| - tags << tag(:meta, property: "twitter:label#{i + 1}", content: pair[0]) - tags << tag(:meta, property: "twitter:data#{i + 1}", content: pair[1]) + tags << tag.meta(property: "twitter:label#{i + 1}", content: pair[0]) + tags << tag.meta(property: "twitter:data#{i + 1}", content: pair[1]) end tags.join.html_safe diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index 57afe0ed0be..f2b7c0064e4 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -100,11 +100,19 @@ module PreferencesHelper def language_choices options_for_select( - selectable_locales_with_translation_level.sort, + selectable_locales_with_translation_level(Gitlab::I18n::MINIMUM_TRANSLATION_LEVEL).sort, current_user.preferred_language ) end + def default_preferred_language_choices + options_for_select( + selectable_locales_with_translation_level( + PreferredLanguageSwitcherHelper::SWITCHER_MINIMUM_TRANSLATION_LEVEL).sort, + Gitlab::CurrentSettings.default_preferred_language + ) + end + def integration_views [].tap do |views| views << { name: 'gitpod', message: gitpod_enable_description, message_url: gitpod_url_placeholder, help_link: help_page_path('integration/gitpod.md') } if Gitlab::CurrentSettings.gitpod_enabled @@ -136,8 +144,8 @@ module PreferencesHelper first_day_of_week_choices.rassoc(Gitlab::CurrentSettings.first_day_of_week).first end - def selectable_locales_with_translation_level - Gitlab::I18n.selectable_locales.map do |code, language| + def selectable_locales_with_translation_level(minimum_level) + Gitlab::I18n.selectable_locales(minimum_level).map do |code, language| [ s_("i18n|%{language} (%{percent_translated}%% translated)") % { language: language, diff --git a/app/helpers/preferred_language_switcher_helper.rb b/app/helpers/preferred_language_switcher_helper.rb new file mode 100644 index 00000000000..1cad4f842ec --- /dev/null +++ b/app/helpers/preferred_language_switcher_helper.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module PreferredLanguageSwitcherHelper + SWITCHER_MINIMUM_TRANSLATION_LEVEL = 90 + + def ordered_selectable_locales + highly_translated_languages = Gitlab::I18n.selectable_locales(SWITCHER_MINIMUM_TRANSLATION_LEVEL) + # see https://docs.gitlab.com/ee/development/i18n/externalization.html#adding-a-new-language + # for translation standards + locale_list = highly_translated_languages.filter_map do |code, language| + percentage = Gitlab::I18n.percentage_translated_for(code) + { + value: code, + percentage: percentage, + text: language.split('-').last.strip + } + end + + locale_list.sort_by { |item| item[:percentage] }.reverse + end +end diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index bfe39bbc211..979b979fba7 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -46,6 +46,14 @@ module ProfilesHelper end end + def ssh_key_usage_types + { + s_('SSHKey|Authentication & Signing') => 'auth_and_signing', + s_('SSHKey|Authentication') => 'auth', + s_('SSHKey|Signing') => 'signing' + } + end + # Overridden in EE::ProfilesHelper#ssh_key_expiration_tooltip def ssh_key_expiration_tooltip(key) return key.errors.full_messages.join(', ') if key.errors.full_messages.any? diff --git a/app/helpers/programming_languages_helper.rb b/app/helpers/programming_languages_helper.rb new file mode 100644 index 00000000000..c50872aec6f --- /dev/null +++ b/app/helpers/programming_languages_helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ProgrammingLanguagesHelper + def search_language_placeholder + placeholder = _('Language') + + return placeholder unless params[:language].present? + + programming_languages.find { |language| language.id.to_s == params[:language] }&.name || + placeholder + end + + def programming_languages + @programming_languages ||= ProgrammingLanguage.most_popular + end + + def language_state_class(language) + params[:language] == language.id.to_s ? 'is-active' : '' + end +end diff --git a/app/helpers/projects/ml/experiments_helper.rb b/app/helpers/projects/ml/experiments_helper.rb index 29bd879859e..a67484e3d2f 100644 --- a/app/helpers/projects/ml/experiments_helper.rb +++ b/app/helpers/projects/ml/experiments_helper.rb @@ -9,7 +9,9 @@ module Projects items = candidates.map do |candidate| { **candidate.params.to_h { |p| [p.name, p.value] }, - **candidate.latest_metrics.to_h { |m| [m.name, number_with_precision(m.value, precision: 4)] } + **candidate.latest_metrics.to_h { |m| [m.name, number_with_precision(m.value, precision: 4)] }, + artifact: link_to_artifact(candidate), + details: link_to_details(candidate) } end @@ -19,6 +21,42 @@ module Projects def unique_logged_names(candidates, &selector) Gitlab::Json.generate(candidates.flat_map(&selector).map(&:name).uniq) end + + def candidate_as_data(candidate) + data = { + params: candidate.params, + metrics: candidate.latest_metrics, + info: { + iid: candidate.iid, + path_to_artifact: link_to_artifact(candidate), + experiment_name: candidate.experiment.name, + path_to_experiment: link_to_experiment(candidate), + status: candidate.status + } + } + + Gitlab::Json.generate(data) + end + + private + + def link_to_artifact(candidate) + artifact = candidate.artifact + + return unless artifact.present? + + project_package_path(candidate.experiment.project, artifact) + end + + def link_to_details(candidate) + project_ml_candidate_path(candidate.experiment.project, candidate.iid) + end + + def link_to_experiment(candidate) + experiment = candidate.experiment + + project_ml_experiment_path(experiment.project, experiment.iid) + end end end end diff --git a/app/helpers/projects/pipeline_helper.rb b/app/helpers/projects/pipeline_helper.rb index edbdb9d4adf..5c62920cd89 100644 --- a/app/helpers/projects/pipeline_helper.rb +++ b/app/helpers/projects/pipeline_helper.rb @@ -12,6 +12,7 @@ module Projects graphql_resource_etag: graphql_etag_pipeline_path(pipeline), metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, format: :json), pipeline_iid: pipeline.iid, + pipeline_path: pipeline_path(pipeline), pipeline_project_path: project.full_path, total_job_count: pipeline.total_size, summary_endpoint: summary_project_pipeline_tests_path(project, pipeline, format: :json), diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e41a3fa5091..682febe9dc9 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -142,6 +142,8 @@ module ProjectsHelper end def project_search_tabs?(tab) + return false unless @project.present? + abilities = Array(search_tab_ability_map[tab]) abilities.any? { |ability| can?(current_user, ability, @project) } @@ -254,11 +256,8 @@ module ProjectsHelper end end - # TODO: Remove this method when removing the feature flag - # https://gitlab.com/gitlab-org/gitlab/merge_requests/11209#note_162234863 - # make sure to remove from the EE specific controller as well: ee/app/controllers/ee/dashboard/projects_controller.rb def show_projects?(projects, params) - Feature.enabled?(:project_list_filter_bar) || !!(params[:personal] || params[:name] || any_projects?(projects)) + !!(params[:personal] || params[:name] || params[:language] || any_projects?(projects)) end def push_to_create_project_command(user = current_user) @@ -465,9 +464,9 @@ module ProjectsHelper def project_coverage_chart_data_attributes(daily_coverage_options, ref) { graph_endpoint: "#{daily_coverage_options[:graph_api_path]}?#{daily_coverage_options[:base_params].to_query}", - graph_start_date: "#{daily_coverage_options[:base_params][:start_date].strftime('%b %d')}", - graph_end_date: "#{daily_coverage_options[:base_params][:end_date].strftime('%b %d')}", - graph_ref: "#{ref}", + graph_start_date: daily_coverage_options[:base_params][:start_date].strftime('%b %d'), + graph_end_date: daily_coverage_options[:base_params][:end_date].strftime('%b %d'), + graph_ref: ref.to_s, graph_csv_path: "#{daily_coverage_options[:download_path]}?#{daily_coverage_options[:base_params].to_query}" } end @@ -480,6 +479,32 @@ module ProjectsHelper format_cached_count(1000, number) end + def fork_divergence_message(counts) + messages = [] + + if counts[:behind].nil? || counts[:ahead].nil? + return s_('ForksDivergence|Fork has diverged from upstream repository') + end + + if counts[:behind] > 0 + messages << s_("ForksDivergence|%{behind} %{commit_word} behind") % { + behind: counts[:behind], commit_word: n_('commit', 'commits', counts[:behind]) + } + end + + if counts[:ahead] > 0 + messages << s_("ForksDivergence|%{ahead} %{commit_word} ahead of") % { + ahead: counts[:ahead], commit_word: n_('commit', 'commits', counts[:ahead]) + } + end + + if messages.blank? + s_('ForksDivergence|Up to date with upstream repository') + else + s_("ForksDivergence|%{messages} upstream repository") % { messages: messages.join(', ') } + end + end + private def localized_access_names @@ -531,10 +556,10 @@ module ProjectsHelper def search_tab_ability_map @search_tab_ability_map ||= tab_ability_map.merge( - blobs: :download_code, - commits: :download_code, + blobs: :read_code, + commits: :read_code, merge_requests: :read_merge_request, - notes: [:read_merge_request, :download_code, :read_issue, :read_snippet], + notes: [:read_merge_request, :read_code, :read_issue, :read_snippet], members: :read_project_member ) end @@ -658,7 +683,6 @@ module ProjectsHelper lfsEnabled: !!project.lfs_enabled, emailsDisabled: project.emails_disabled?, metricsDashboardAccessLevel: feature.metrics_dashboard_access_level, - operationsAccessLevel: feature.operations_access_level, monitorAccessLevel: feature.monitor_access_level, showDefaultAwardEmojis: project.show_default_award_emojis?, warnAboutPotentiallyUnwantedCharacters: project.warn_about_potentially_unwanted_characters?, @@ -680,7 +704,7 @@ module ProjectsHelper def find_file_path return unless @project && !@project.empty_repo? - return unless can?(current_user, :download_code, @project) + return unless can?(current_user, :read_code, @project) ref = @ref || @project.repository.root_ref diff --git a/app/helpers/routing/pseudonymization_helper.rb b/app/helpers/routing/pseudonymization_helper.rb index dce0517690d..63e2b377fef 100644 --- a/app/helpers/routing/pseudonymization_helper.rb +++ b/app/helpers/routing/pseudonymization_helper.rb @@ -12,6 +12,7 @@ module Routing tab glm_source glm_content + _gl ].freeze def initialize(request_object, group, project) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index b8ac2afa7d6..e03365ad5f1 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -40,6 +40,7 @@ module SearchHelper [ groups_autocomplete(term), projects_autocomplete(term), + users_autocomplete(term), issue_autocomplete(term) ].flatten end @@ -308,8 +309,8 @@ module SearchHelper { category: "Groups", id: group.id, - value: "#{search_result_sanitize(group.name)}", - label: "#{search_result_sanitize(group.full_name)}", + value: search_result_sanitize(group.name), + label: search_result_sanitize(group.full_name), url: group_path(group), avatar_url: group.avatar_url || '' } @@ -343,14 +344,32 @@ module SearchHelper { category: "Projects", id: p.id, - value: "#{search_result_sanitize(p.name)}", - label: "#{search_result_sanitize(p.full_name)}", + value: search_result_sanitize(p.name), + label: search_result_sanitize(p.full_name), url: project_path(p), avatar_url: p.avatar_url || '' } end end + def users_autocomplete(term, limit = 5) + return [] unless current_user && Ability.allowed?(current_user, :read_users_list) + + SearchService + .new(current_user, { scope: 'users', per_page: limit, search: term }) + .search_objects + .map do |user| + { + category: "Users", + id: user.id, + value: search_result_sanitize(user.name), + label: search_result_sanitize(user.username), + url: user_path(user), + avatar_url: user.avatar_url || '' + } + end + end + def recent_merge_requests_autocomplete(term) return [] unless current_user @@ -427,20 +446,38 @@ module SearchHelper result end + def code_tab_condition + return true if project_search_tabs?(:blobs) + + @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_code_tab) + end + + def wiki_tab_condition + return true if project_search_tabs?(:wiki) + + @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_wiki_tab) + end + + def commits_tab_condition + return true if project_search_tabs?(:commits) + + @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab) + end + # search page scope navigation def search_navigation { projects: { sort: 1, label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? }, - blobs: { sort: 2, 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)) }, + blobs: { sort: 2, label: _("Code"), data: { qa_selector: 'code_tab' }, condition: code_tab_condition }, # sort: 3 is reserved for EE items issues: { sort: 4, label: _("Issues"), condition: project_search_tabs?(:issues) || feature_flag_tab_enabled?(:global_search_issues_tab) }, merge_requests: { sort: 5, label: _("Merge requests"), condition: project_search_tabs?(:merge_requests) || feature_flag_tab_enabled?(:global_search_merge_requests_tab) }, - wiki_blobs: { sort: 6, label: _("Wiki"), condition: project_search_tabs?(:wiki) || search_service.show_elasticsearch_tabs? }, - commits: { sort: 7, label: _("Commits"), condition: project_search_tabs?(:commits) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)) }, + wiki_blobs: { sort: 6, label: _("Wiki"), condition: wiki_tab_condition }, + commits: { sort: 7, label: _("Commits"), condition: commits_tab_condition }, notes: { sort: 8, label: _("Comments"), condition: project_search_tabs?(:notes) || search_service.show_elasticsearch_tabs? }, milestones: { sort: 9, label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? }, - users: { sort: 10, label: _("Users"), condition: show_user_search_tab? }, - snippet_titles: { sort: 11, label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? } + users: { sort: 10, label: _("Users"), condition: show_user_search_tab? }, + snippet_titles: { sort: 11, label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: search_service.show_snippets? && @project.nil? } } end @@ -545,12 +582,10 @@ module SearchHelper else :success end + elsif issuable.closed? + :info else - if issuable.closed? - :info - else - :success - end + :success end end @@ -566,7 +601,7 @@ module SearchHelper end def feature_flag_tab_enabled?(flag) - @group || Feature.enabled?(flag, current_user, type: :ops) + @group.present? || Feature.enabled?(flag, current_user, type: :ops) end def sanitized_search_params diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 9002fdda128..cbee02a28c0 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -20,9 +20,8 @@ module SidebarsHelper end end - def project_sidebar_context(project, user, current_ref) - context_data = project_sidebar_context_data(project, user, current_ref) - + def project_sidebar_context(project, user, current_ref, ref_type: nil) + context_data = project_sidebar_context_data(project, user, current_ref, ref_type: ref_type) Sidebars::Projects::Context.new(**context_data) end @@ -83,12 +82,13 @@ module SidebarsHelper tracking_attrs('user_side_navigation', 'render', 'user_side_navigation') end - def project_sidebar_context_data(project, user, current_ref) + def project_sidebar_context_data(project, user, current_ref, ref_type: nil) { current_user: user, container: project, learn_gitlab_enabled: learn_gitlab_enabled?(project), current_ref: current_ref, + ref_type: ref_type, jira_issues_integration: project_jira_issues_integration?, can_view_pipeline_editor: can_view_pipeline_editor?(project), show_cluster_hint: show_gke_cluster_integration_callout?(project) diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index a711f36fe05..4a9596a1347 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -44,25 +44,18 @@ module SortingHelper # rubocop: enable Metrics/AbcSize def projects_sort_options_hash - use_old_sorting = Feature.disabled?(:project_list_filter_bar) || current_controller?('admin/projects') - options = { sort_value_latest_activity => sort_title_latest_activity, sort_value_recently_created => sort_title_created_date, sort_value_name => sort_title_name, sort_value_name_desc => sort_title_name_desc, - sort_value_stars_desc => sort_title_stars + sort_value_stars_desc => sort_title_stars, + sort_value_oldest_activity => sort_title_oldest_activity, + sort_value_oldest_created => sort_title_oldest_created, + sort_value_recently_created => sort_title_recently_created, + sort_value_stars_desc => sort_title_most_stars } - if use_old_sorting - options = options.merge({ - sort_value_oldest_activity => sort_title_oldest_activity, - sort_value_oldest_created => sort_title_oldest_created, - sort_value_recently_created => sort_title_recently_created, - sort_value_stars_desc => sort_title_most_stars - }) - end - if current_controller?('admin/projects') options[sort_value_largest_repo] = sort_title_largest_repo end @@ -79,29 +72,6 @@ module SortingHelper } end - def projects_sort_option_titles - # Only used for the project filter search bar - projects_sort_options_hash.merge({ - sort_value_oldest_activity => sort_title_latest_activity, - sort_value_oldest_created => sort_title_created_date, - sort_value_name_desc => sort_title_name, - sort_value_stars_asc => sort_title_stars - }) - end - - def projects_reverse_sort_options_hash - { - sort_value_latest_activity => sort_value_oldest_activity, - sort_value_recently_created => sort_value_oldest_created, - sort_value_name => sort_value_name_desc, - sort_value_stars_desc => sort_value_stars_asc, - sort_value_oldest_activity => sort_value_latest_activity, - sort_value_oldest_created => sort_value_recently_created, - sort_value_name_desc => sort_value_name, - sort_value_stars_asc => sort_value_stars_desc - } - end - def forks_reverse_sort_options_hash { sort_value_recently_created => sort_value_oldest_created, @@ -188,13 +158,6 @@ module SortingHelper } end - def runners_sort_options_hash - { - sort_value_created_date => sort_title_created_date, - sort_value_contacted_date => sort_title_contacted_date - } - end - def starrers_sort_options_hash { sort_value_name => sort_title_name, @@ -308,7 +271,7 @@ module SortingHelper end def sort_direction_button(reverse_url, reverse_sort, sort_value) - link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' + link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn rspec-reverse-sort' icon = sort_direction_icon(sort_value) url = reverse_url @@ -329,13 +292,6 @@ module SortingHelper sort_direction_button(url, reverse_sort, sort_value) end - def project_sort_direction_button(sort_value) - reverse_sort = projects_reverse_sort_options_hash[sort_value] - url = filter_projects_path(sort: reverse_sort) - - sort_direction_button(url, reverse_sort, sort_value) - end - def packages_sort_options_hash { sort_value_recently_created => sort_title_created_date, diff --git a/app/helpers/ssh_keys_helper.rb b/app/helpers/ssh_keys_helper.rb index 17a9fd6146d..4cd40836335 100644 --- a/app/helpers/ssh_keys_helper.rb +++ b/app/helpers/ssh_keys_helper.rb @@ -2,10 +2,14 @@ module SshKeysHelper def ssh_key_delete_modal_data(key, path) + title = _('Delete Key') + { path: path, method: 'delete', qa_selector: 'delete_ssh_key_button', + title: title, + aria_label: title, modal_attributes: { 'data-qa-selector': 'ssh_key_delete_modal', title: _('Are you sure you want to delete this SSH key?'), diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 2942765a108..e3e2f423da3 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -75,7 +75,7 @@ module SubmoduleHelper return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/', project].join('') - url_with_dotgit = url_no_dotgit + '.git' + url_with_dotgit = "#{url_no_dotgit}.git" url_with_dotgit == Gitlab::RepositoryUrlBuilder.build([namespace, '/', project].join('')) end @@ -108,7 +108,7 @@ module SubmoduleHelper def relative_self_links(relative_path, commit, old_commit, project) relative_path = relative_path.rstrip - absolute_project_path = "/" + project.full_path + absolute_project_path = "/#{project.full_path}" # Resolve `relative_path` to target path # Assuming `absolute_project_path` is `/g1/p1`: diff --git a/app/helpers/timeboxes_helper.rb b/app/helpers/timeboxes_helper.rb index e0e6229bc6d..307f03e0d64 100644 --- a/app/helpers/timeboxes_helper.rb +++ b/app/helpers/timeboxes_helper.rb @@ -36,7 +36,7 @@ module TimeboxesHelper end end - def milestones_browse_issuables_path(milestone, state: nil, type:) + def milestones_browse_issuables_path(milestone, type:, state: nil) opts = { milestone_title: milestone.title, state: state } if @project diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index be63d28600f..d7c4540544b 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -16,17 +16,20 @@ module TodosHelper def todo_action_name(todo) case todo.action when Todo::ASSIGNED then todo.self_added? ? _('assigned') : _('assigned you') - when Todo::REVIEW_REQUESTED then s_('Todos|requested a review of') + when Todo::REVIEW_REQUESTED then s_('Todos|requested a review') when Todo::MENTIONED, Todo::DIRECTLY_ADDRESSED then format( - s_("Todos|mentioned %{who} on"), who: todo_action_subject(todo) + s_("Todos|mentioned %{who}"), who: todo_action_subject(todo) ) - when Todo::BUILD_FAILED then s_('Todos|The pipeline failed in') - when Todo::MARKED then s_('Todos|added a todo for') + when Todo::BUILD_FAILED then s_('Todos|The pipeline failed') + when Todo::MARKED then s_('Todos|added a to-do item') when Todo::APPROVAL_REQUIRED then format( - s_("Todos|set %{who} as an approver for"), who: todo_action_subject(todo) + s_("Todos|set %{who} as an approver"), who: todo_action_subject(todo) ) when Todo::UNMERGEABLE then s_('Todos|Could not merge') - when Todo::MERGE_TRAIN_REMOVED then s_("Todos|Removed from Merge Train:") + when Todo::MERGE_TRAIN_REMOVED then s_("Todos|Removed from Merge Train") + when Todo::MEMBER_ACCESS_REQUESTED then format( + s_("Todos|has requested access to group %{which}"), which: _(todo.target.name) + ) end end @@ -37,45 +40,48 @@ module TodosHelper end end - def todo_target_link(todo) - text = raw(todo_target_type_name(todo) + ' ') + - if todo.for_commit? - content_tag(:span, todo.target_reference, class: 'commit-sha') - else - todo.target_reference - end + def todo_target_name(todo) + return todo.target_reference unless todo.for_commit? - link_to text, todo_target_path(todo) + content_tag(:span, todo.target_reference, class: 'commit-sha') end def todo_target_title(todo) - # Design To Dos' filenames are displayed in `#todo_target_link` (see `Design#to_reference`), + # Design To Dos' filenames are displayed in `#todo_target_name` (see `Design#to_reference`), # so to avoid displaying duplicate filenames in the To Do list for designs, # we return an empty string here. - return "" if todo.target.blank? || todo.for_design? + return "" if todo.target.blank? || todo.for_design? || todo.member_access_requested? - "\"#{todo.target.title}\"" + todo.target.title.to_s end def todo_parent_path(todo) if todo.resource_parent.is_a?(Group) - link_to todo.resource_parent.name, group_path(todo.resource_parent) + todo.resource_parent.name else - link_to_project(todo.project) + title = content_tag(:span, todo.project.name, class: 'project-name') + namespace = content_tag(:span, "#{todo.project.namespace.human_name} / ", class: 'namespace-name') + + title.prepend(namespace) if todo.project.namespace + + title end end - def todo_target_type_name(todo) - return _('design') if todo.for_design? - return _('alert') if todo.for_alert? - - target_type = if todo.for_issue_or_work_item? + def todo_target_aria_label(todo) + target_type = if todo.for_design? + _('Design') + elsif todo.for_alert? + _('Alert') + elsif todo.member_access_requested? + _('Group') + elsif todo.for_issue_or_work_item? IntegrationsHelper.integration_issue_type(todo.target.issue_type) else IntegrationsHelper.integration_todo_target_type(todo.target_type) end - target_type.titleize.downcase + "#{target_type} #{todo_target_name(todo)}" end def todo_target_path(todo) @@ -92,6 +98,8 @@ module TodosHelper elsif todo.for_issue_or_work_item? path_options[:only_path] = true Gitlab::UrlBuilder.build(todo.target, **path_options) + elsif todo.member_access_requested? + todo.access_request_url else path = [todo.resource_parent, todo.target] @@ -123,18 +131,18 @@ module TodosHelper when MergeRequest case state when 'closed' - background_class = 'gl-bg-red-500' + variant = 'danger' when 'merged' - background_class = 'gl-bg-blue-500' + variant = 'info' end when Issue - background_class = 'gl-bg-blue-500' if state == 'closed' + variant = 'info' if state == 'closed' when AlertManagement::Alert - background_class = 'gl-bg-blue-500' if state == 'resolved' + variant = 'info' if state == 'resolved' end - tag.span class: "gl-my-0 gl-px-2 status-box #{background_class}" do - raw_state_to_i18n[state] || state.capitalize + content_tag(:span, class: 'todo-target-state') do + gl_badge_tag(raw_state_to_i18n[state] || state.capitalize, { variant: variant, size: 'sm' }) end end @@ -183,7 +191,8 @@ module TodosHelper { id: Todo::REVIEW_REQUESTED, text: s_('Todos|Review requested') }, { id: Todo::MENTIONED, text: s_('Todos|Mentioned') }, { id: Todo::MARKED, text: s_('Todos|Added') }, - { id: Todo::BUILD_FAILED, text: s_('Todos|Pipelines') } + { id: Todo::BUILD_FAILED, text: s_('Todos|Pipelines') }, + { id: Todo::MEMBER_ACCESS_REQUESTED, text: s_('Todos|Member access requested') } ] end @@ -222,10 +231,15 @@ module TodosHelper end content = content_tag(:span, class: css_class) do - "Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}" + format(s_("Todos|Due %{due_date}"), due_date: if is_due_today + _("today") + else + l(todo.target.due_date, + format: Date::DATE_FORMATS[:medium]) + end) end - "· #{content}".html_safe + "#{content} ·".html_safe end def todo_author_display?(todo) diff --git a/app/helpers/tooling/visual_review_helper.rb b/app/helpers/tooling/visual_review_helper.rb index da6eb3ec434..cd3b8be5aac 100644 --- a/app/helpers/tooling/visual_review_helper.rb +++ b/app/helpers/tooling/visual_review_helper.rb @@ -14,10 +14,10 @@ module Tooling GITLAB_ORG_GITLAB_PROJECT_PATH = 'gitlab-org/gitlab' def visual_review_toolbar_options - { 'data-merge-request-id': "#{ENV['REVIEW_APPS_MERGE_REQUEST_IID']}", - 'data-mr-url': "#{GITLAB_INSTANCE_URL}", - 'data-project-id': "#{GITLAB_ORG_GITLAB_PROJECT_ID}", - 'data-project-path': "#{GITLAB_ORG_GITLAB_PROJECT_PATH}", + { 'data-merge-request-id': ENV['REVIEW_APPS_MERGE_REQUEST_IID'].to_s, + 'data-mr-url': GITLAB_INSTANCE_URL, + 'data-project-id': GITLAB_ORG_GITLAB_PROJECT_ID, + 'data-project-path': GITLAB_ORG_GITLAB_PROJECT_PATH, 'data-require-auth': false, 'id': 'review-app-toolbar-script', 'src': 'https://gitlab.com/assets/webpack/visual_review_toolbar.js' } diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index 48548ae9e6a..0bb92dfd118 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true module VersionCheckHelper + include Gitlab::Utils::StrongMemoize + + SECURITY_ALERT_SEVERITY = 'danger' + def show_version_check? return false unless Gitlab::CurrentSettings.version_check_enabled return false if User.single_user&.requires_usage_stats_consent? @@ -8,6 +12,17 @@ module VersionCheckHelper current_user&.can_read_all_resources? end + def gitlab_version_check + VersionCheck.new.response + end + strong_memoize_attr :gitlab_version_check + + def show_security_patch_upgrade_alert? + return false unless show_version_check? && gitlab_version_check + + gitlab_version_check['severity'] === SECURITY_ALERT_SEVERITY + end + def link_to_version if Gitlab.pre_release? commit_link = link_to(Gitlab.revision, source_host_url + namespace_project_commits_path(source_code_group, source_code_project, Gitlab.revision)) diff --git a/app/helpers/web_hooks/web_hooks_helper.rb b/app/helpers/web_hooks/web_hooks_helper.rb index e95b90c69ef..bda9bf58fb7 100644 --- a/app/helpers/web_hooks/web_hooks_helper.rb +++ b/app/helpers/web_hooks/web_hooks_helper.rb @@ -7,8 +7,6 @@ module WebHooks def show_project_hook_failed_callout?(project:) return false if project_hook_page? return false unless current_user - return false unless Feature.enabled?(:webhooks_failed_callout, project) - return false unless Feature.enabled?(:web_hooks_disable_failed, project) return false unless Ability.allowed?(current_user, :read_web_hooks, project) # Assumes include of Users::CalloutsHelper diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index 017a1861905..b2b8ca2a120 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -60,7 +60,7 @@ module WikiHelper end def wiki_sort_controls(wiki, direction) - link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' + link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn rspec-reverse-sort' reversed_direction = direction == 'desc' ? 'asc' : 'desc' icon_class = direction == 'desc' ? 'highest' : 'lowest' title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending') diff --git a/app/helpers/x509_helper.rb b/app/helpers/x509_helper.rb index 1a9dbefceef..599be0b91f7 100644 --- a/app/helpers/x509_helper.rb +++ b/app/helpers/x509_helper.rb @@ -16,8 +16,4 @@ module X509Helper rescue StandardError {} end - - def x509_signature?(sig) - sig.is_a?(CommitSignatures::X509CommitSignature) || sig.is_a?(Gitlab::X509::Signature) - end end |