diff options
Diffstat (limited to 'app/helpers')
45 files changed, 458 insertions, 426 deletions
diff --git a/app/helpers/admin/abuse_reports_helper.rb b/app/helpers/admin/abuse_reports_helper.rb index 275bed406f1..015d4513091 100644 --- a/app/helpers/admin/abuse_reports_helper.rb +++ b/app/helpers/admin/abuse_reports_helper.rb @@ -18,7 +18,8 @@ module Admin def abuse_report_data(report) { - abuse_report_data: Admin::AbuseReportDetailsSerializer.new.represent(report).to_json + abuse_report_data: Admin::AbuseReportDetailsSerializer.new.represent(report).to_json, + abuse_reports_list_path: admin_abuse_reports_path } end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2bf239979f7..e3a630024d9 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -315,7 +315,8 @@ module ApplicationHelper class_names << 'issue-boards-page gl-overflow-auto' if current_controller?(:boards) class_names << 'epic-boards-page gl-overflow-auto' if current_controller?(:epic_boards) class_names << 'with-performance-bar' if performance_bar_enabled? - class_names << 'with-top-bar' if show_super_sidebar? && !@hide_top_bar + class_names << 'with-header' if !show_super_sidebar? || !current_user + class_names << 'with-top-bar' if show_super_sidebar? && !@hide_top_bar_padding class_names << system_message_class class_names << 'logged-out-marketing-header' if !current_user && ::Gitlab.com? && !show_super_sidebar? @@ -485,6 +486,15 @@ module ApplicationHelper end end + def controller_full_path + action = case controller.action_name + when 'create' then 'new' + when 'update' then 'edit' + else controller.action_name + end + "#{controller.controller_path}/#{action}" + end + private def browser_id diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index a45425474b5..ef91915ce38 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -238,6 +238,7 @@ module ApplicationSettingsHelper :container_expiration_policies_enable_historic_entries, :container_registry_expiration_policies_caching, :container_registry_token_expire_delay, + :decompress_archive_file_timeout, :default_artifacts_expire_in, :default_branch_name, :default_branch_protection, @@ -306,7 +307,6 @@ module ApplicationSettingsHelper :housekeeping_optimize_repository_period, :html_emails_enabled, :import_sources, - :in_product_marketing_emails_enabled, :inactive_projects_delete_after_months, :inactive_projects_min_size_mb, :inactive_projects_send_warning_email_after_months, @@ -413,6 +413,7 @@ module ApplicationSettingsHelper :throttle_protected_paths_period_in_seconds, :throttle_protected_paths_requests_per_period, :protected_paths_raw, + :protected_paths_for_get_request_raw, :time_tracking_limit_to_hours, :two_factor_grace_period, :update_runner_versions_enabled, @@ -436,6 +437,7 @@ module ApplicationSettingsHelper :mailgun_events_enabled, :snowplow_collector_hostname, :snowplow_cookie_domain, + :snowplow_database_collector_hostname, :snowplow_enabled, :snowplow_app_id, :push_event_hooks_limit, @@ -478,12 +480,14 @@ module ApplicationSettingsHelper :sentry_dsn, :sentry_clientside_dsn, :sentry_environment, + :sentry_clientside_traces_sample_rate, :sidekiq_job_limiter_mode, :sidekiq_job_limiter_compression_threshold_bytes, :sidekiq_job_limiter_limit_bytes, :suggest_pipeline_enabled, :search_rate_limit, :search_rate_limit_unauthenticated, + :search_rate_limit_allowlist_raw, :users_get_by_id_limit, :users_get_by_id_limit_allowlist_raw, :runner_token_expiration_interval, diff --git a/app/helpers/artifacts_helper.rb b/app/helpers/artifacts_helper.rb index f90d59409ed..10d2714840d 100644 --- a/app/helpers/artifacts_helper.rb +++ b/app/helpers/artifacts_helper.rb @@ -5,8 +5,7 @@ module ArtifactsHelper { project_path: project.full_path, project_id: project.id, - can_destroy_artifacts: can?(current_user, :destroy_artifacts, project).to_s, - artifacts_management_feedback_image_path: image_path('illustrations/chat-bubble-sm.svg') + can_destroy_artifacts: can?(current_user, :destroy_artifacts, project).to_s } end end diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index c928c6479de..b7acc562be5 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module AuthHelper - PROVIDERS_WITH_ICONS = %w( + PROVIDERS_WITH_ICONS = %w[ alicloud atlassian_oauth2 auth0 @@ -15,12 +15,11 @@ module AuthHelper google_oauth2 jwt openid_connect - salesforce shibboleth twitter - ).freeze - LDAP_PROVIDER = /\Aldap/.freeze - POPULAR_PROVIDERS = %w(google_oauth2 github).freeze + ].freeze + LDAP_PROVIDER = /\Aldap/ + POPULAR_PROVIDERS = %w[google_oauth2 github].freeze delegate :slack_app_id, to: :'Gitlab::CurrentSettings.current_application_settings' diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 6e0ba748d85..e6212ee7d8d 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -7,6 +7,15 @@ module ButtonHelper # :text - Text to copy (optional) # :gfm - GitLab Flavored Markdown to copy, if different from `text` (optional) # :target - Selector for target element to copy from (optional) + # :class - CSS classes to be applied to the button (optional) + # :title - Button's title attribute (used for the tooltip) (optional) + # :button_text - Button's displayed label (optional) + # :hide_tooltip - Whether the tooltip should be hidden (optional, default: false) + # :hide_button_icon - Whether the icon should be hidden (optional, default: false) + # :item_prop - itemprop attribute + # :variant - Button variant (optional, default: :default) + # :category - Button category (optional, default: :tertiary) + # :size - Button size (optional, default: :small) # # Examples: # @@ -20,6 +29,65 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) + css_class = data.delete(:class) + title = data.delete(:title) || _('Copy') + button_text = data[:button_text] || nil + hide_tooltip = data[:hide_tooltip] || false + hide_button_icon = data[:hide_button_icon] || false + item_prop = data[:itemprop] || nil + variant = data[:variant] || :default + category = data[:category] || :tertiary + size = data[:size] || :small + + # This supports code in app/assets/javascripts/copy_to_clipboard.js that + # works around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM. + if text = data.delete(:text) + data[:clipboard_text] = + if gfm = data.delete(:gfm) + { text: text, gfm: gfm } + else + text + end + end + + target = data.delete(:target) + data[:clipboard_target] = target if target + + unless hide_tooltip + data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) + end + + render ::Pajamas::ButtonComponent.new( + icon: hide_button_icon ? nil : 'copy-to-clipboard', + variant: variant, + category: category, + size: size, + button_options: { class: css_class, title: title, aria: { label: title, live: 'polite' }, data: data, itemprop: item_prop }) do + button_text + end + end + + # Output a "Copy to Clipboard" button + # Note: This is being replaced by a Pajamas-compliant helper that renders the button + # via ::Pajamas::ButtonComponent. Please use clipboard_button instead. + # + # data - Data attributes passed to `content_tag` (default: {}): + # :text - Text to copy (optional) + # :gfm - GitLab Flavored Markdown to copy, if different from `text` (optional) + # :target - Selector for target element to copy from (optional) + # + # Examples: + # + # # Define the clipboard's text + # clipboard_button(text: "Foo") + # # => "<button class='...' data-clipboard-text='Foo'>...</button>" + # + # # Define the target element + # clipboard_button(target: "div#foo") + # # => "<button class='...' data-clipboard-target='div#foo'>...</button>" + # + # See http://clipboardjs.com/#usage + def deprecated_clipboard_button(data = {}) css_class = data.delete(:class) || 'btn-clipboard gl-button btn-default-tertiary btn-icon btn-sm' title = data.delete(:title) || _('Copy') button_text = data[:button_text] || nil diff --git a/app/helpers/ci/status_helper.rb b/app/helpers/ci/status_helper.rb index ea5b613cb78..5d526a6abb6 100644 --- a/app/helpers/ci/status_helper.rb +++ b/app/helpers/ci/status_helper.rb @@ -9,55 +9,6 @@ # module Ci module StatusHelper - def ci_label_for_status(status) - if detailed_status?(status) - return status.label - end - - label = case status - when 'success' - 'passed' - when 'success-with-warnings' - 'passed with warnings' - when 'manual' - 'waiting for manual action' - when 'scheduled' - 'waiting for delayed job' - else - status - end - translation = "CiStatusLabel|#{label}" - s_(translation) - end - - def ci_text_for_status(status) - if detailed_status?(status) - return status.text - end - - case status - when 'success' - s_('CiStatusText|passed') - when 'success-with-warnings' - s_('CiStatusText|passed') - when 'manual' - s_('CiStatusText|blocked') - when 'scheduled' - s_('CiStatusText|delayed') - else - # All states are already being translated inside the detailed statuses: - # :running => Gitlab::Ci::Status::Running - # :skipped => Gitlab::Ci::Status::Skipped - # :failed => Gitlab::Ci::Status::Failed - # :success => Gitlab::Ci::Status::Success - # :canceled => Gitlab::Ci::Status::Canceled - # The following states are customized above: - # :manual => Gitlab::Ci::Status::Manual - status_translation = "CiStatusText|#{status}" - s_(status_translation) - end - end - def ci_status_for_statuseable(subject) status = subject.try(:status) || 'not found' status.humanize @@ -138,11 +89,34 @@ module Ci end end + private + def detailed_status?(status) status.respond_to?(:text) && status.respond_to?(:group) && status.respond_to?(:label) && status.respond_to?(:icon) end + + def ci_label_for_status(status) + if detailed_status?(status) + return status.label + end + + label = case status + when 'success' + 'passed' + when 'success-with-warnings' + 'passed with warnings' + when 'manual' + 'waiting for manual action' + when 'scheduled' + 'waiting for delayed job' + else + status + end + translation = "CiStatusLabel|#{label}" + s_(translation) + end end end diff --git a/app/helpers/ci/variables_helper.rb b/app/helpers/ci/variables_helper.rb index a492c48e58c..0dbd1adeb71 100644 --- a/app/helpers/ci/variables_helper.rb +++ b/app/helpers/ci/variables_helper.rb @@ -42,8 +42,8 @@ module Ci def ci_variable_type_options [ - %w(Variable env_var), - %w(File file) + %w[Variable env_var], + %w[File file] ] end diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index 5c410a28229..1989d6ab3d5 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -92,7 +92,7 @@ module ClustersHelper end def cluster_created?(cluster) - !cluster.status_name.in?(%i/scheduled creating/) + !cluster.status_name.in?(%i[scheduled creating]) end def can_admin_cluster?(user, cluster) diff --git a/app/helpers/colors_helper.rb b/app/helpers/colors_helper.rb index 80cf6f197e5..3cd7263c39e 100644 --- a/app/helpers/colors_helper.rb +++ b/app/helpers/colors_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ColorsHelper - HEX_COLOR_PATTERN = /\A\#(?:[0-9A-Fa-f]{3}){1,2}\Z/.freeze + HEX_COLOR_PATTERN = /\A\#(?:[0-9A-Fa-f]{3}){1,2}\Z/ def hex_color_to_rgb_array(hex_color) unless hex_color.is_a?(String) && HEX_COLOR_PATTERN.match?(hex_color) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index c5df53ec606..9a78d4d9ad5 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -16,7 +16,7 @@ module DiffHelper def diff_view @diff_view ||= begin - diff_views = %w(inline parallel) + diff_views = %w[inline parallel] diff_view = params[:view] || cookies[:diff_view] diff_view = diff_views.first unless diff_views.include?(diff_view) diff_view.to_sym diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index af0f1bd6808..69b3fdc2271 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -16,7 +16,7 @@ module EmailsHelper def action_title(url) return unless url - %w(merge_requests issues commit).each do |action| + %w[merge_requests issues commit].each do |action| if url.split("/").include?(action) return "View #{action.humanize.singularize}" end diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 8140ee97291..6e9379a5926 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -62,7 +62,7 @@ module EnvironmentHelper klass = "ci-status ci-#{status.dasherize} #{ci_icon_utilities}" text = "#{ci_icon_for_status(status)} <span class=\"gl-ml-2\">#{status_text}</span>".html_safe - if deployment.deployable + if deployment.deployable.instance_of?(::Ci::Build) link_to(text, deployment_path(deployment), class: klass) else content_tag(:span, text, class: klass) diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index cd768ba8a7b..80a56493653 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -57,11 +57,9 @@ module EnvironmentsHelper 'default_branch' => project.default_branch, 'project_path' => project_path(project), 'tags_path' => project_tags_path(project), - '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).to_s, - 'dashboard_timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase + 'custom_metrics_available' => custom_metrics_available?(project).to_s } end @@ -93,8 +91,7 @@ module EnvironmentsHelper 'empty_loading_svg_path' => image_path('illustrations/monitoring/loading.svg'), 'empty_no_data_svg_path' => image_path('illustrations/monitoring/no_data.svg'), 'empty_no_data_small_svg_path' => image_path('illustrations/chart-empty-state-small.svg'), - 'empty_unable_to_connect_svg_path' => image_path('illustrations/monitoring/unable_to_connect.svg'), - 'custom_dashboard_base_path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT + 'empty_unable_to_connect_svg_path' => image_path('illustrations/monitoring/unable_to_connect.svg') } end end diff --git a/app/helpers/external_link_helper.rb b/app/helpers/external_link_helper.rb index 53dacfe0566..40079c0803d 100644 --- a/app/helpers/external_link_helper.rb +++ b/app/helpers/external_link_helper.rb @@ -7,6 +7,6 @@ module ExternalLinkHelper link = link_to url, { target: '_blank', rel: 'noopener noreferrer' }.merge(options) do "#{body}#{sprite_icon('external-link', css_class: 'gl-ml-2')}".html_safe end - sanitize(link, tags: %w(a svg use), attributes: %w(target rel data-testid class href).concat(options.stringify_keys.keys)) + sanitize(link, tags: %w[a svg use], attributes: %w[target rel data-testid class href].concat(options.stringify_keys.keys)) end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index bba3fac7468..ebebdfa56e6 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -29,9 +29,10 @@ module IconsHelper ActionController::Base.helpers.image_path('file_icons/file_icons.svg', host: sprite_base_url) end - def sprite_icon(icon_name, size: DEFAULT_ICON_SIZE, css_class: nil) + def sprite_icon(icon_name, size: DEFAULT_ICON_SIZE, css_class: nil, file_icon: false) memoized_icon("#{icon_name}_#{size}_#{css_class}") do - if known_sprites&.exclude?(icon_name) + unknown_icon = file_icon ? unknown_file_icon_sprite(icon_name) : unknown_icon_sprite(icon_name) + if unknown_icon exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg") Gitlab::ErrorTracking.track_and_raise_for_dev_exception(exception) end @@ -39,10 +40,11 @@ module IconsHelper css_classes = [] css_classes << "s#{size}" if size css_classes << css_class.to_s unless css_class.blank? + sprite_path = file_icon ? sprite_file_icons_path : sprite_icon_path content_tag( :svg, - content_tag(:use, '', { 'href' => "#{sprite_icon_path}##{icon_name}" }), + content_tag(:use, '', { 'href' => "#{sprite_path}##{icon_name}" }), class: css_classes.empty? ? nil : css_classes.join(' '), data: { testid: "#{icon_name}-icon" } ) @@ -123,61 +125,73 @@ module IconsHelper def file_type_icon_class(type, mode, name) if type == 'folder' - icon_class = 'folder-o' + 'folder-o' elsif type == 'archive' - icon_class = 'archive' + 'archive' elsif mode == '120000' - icon_class = 'share' + 'share' else # Guess which icon to choose based on file extension. # If you think a file extension is missing, feel free to add it on PR case File.extname(name).downcase when '.pdf' - icon_class = 'document' + 'document' when '.jpg', '.jpeg', '.jif', '.jfif', - '.jp2', '.jpx', '.j2k', '.j2c', - '.apng', '.png', '.gif', '.tif', '.tiff', - '.svg', '.ico', '.bmp', '.webp' - icon_class = 'doc-image' + '.jp2', '.jpx', '.j2k', '.j2c', + '.apng', '.png', '.gif', '.tif', '.tiff', + '.svg', '.ico', '.bmp', '.webp' + 'doc-image' when '.zip', '.zipx', '.tar', '.gz', '.gzip', '.tgz', '.bz', '.bzip', - '.bz2', '.bzip2', '.car', '.tbz', '.xz', 'txz', '.rar', '.7z', - '.lz', '.lzma', '.tlz' - icon_class = 'doc-compressed' + '.bz2', '.bzip2', '.car', '.tbz', '.xz', 'txz', '.rar', '.7z', + '.lz', '.lzma', '.tlz' + 'doc-compressed' when '.mp3', '.wma', '.ogg', '.oga', '.wav', '.flac', '.aac', '.3ga', - '.ac3', '.midi', '.m4a', '.ape', '.mpa' - icon_class = 'volume-up' + '.ac3', '.midi', '.m4a', '.ape', '.mpa' + 'volume-up' when '.mp4', '.m4p', '.m4v', - '.mpg', '.mp2', '.mpeg', '.mpe', '.mpv', - '.mpg', '.mpeg', '.m2v', '.m2ts', - '.avi', '.mkv', '.flv', '.ogv', '.mov', - '.3gp', '.3g2' - icon_class = 'live-preview' + '.mpg', '.mp2', '.mpeg', '.mpe', '.mpv', + '.mpg', '.mpeg', '.m2v', '.m2ts', + '.avi', '.mkv', '.flv', '.ogv', '.mov', + '.3gp', '.3g2' + 'live-preview' when '.doc', '.dot', '.docx', '.docm', '.dotx', '.dotm', '.docb', - '.odt', '.ott', '.uot', '.rtf' - icon_class = 'doc-text' + '.odt', '.ott', '.uot', '.rtf' + 'doc-text' when '.xls', '.xlt', '.xlm', '.xlsx', '.xlsm', '.xltx', '.xltm', - '.xlsb', '.xla', '.xlam', '.xll', '.xlw', '.ots', '.ods', '.uos' - icon_class = 'document' + '.xlsb', '.xla', '.xlam', '.xll', '.xlw', '.ots', '.ods', '.uos' + 'document' when '.ppt', '.pot', '.pps', '.pptx', '.pptm', '.potx', '.potm', - '.ppam', '.ppsx', '.ppsm', '.sldx', '.sldm', '.odp', '.otp', '.uop' - icon_class = 'doc-chart' + '.ppam', '.ppsx', '.ppsm', '.sldx', '.sldm', '.odp', '.otp', '.uop' + 'doc-chart' else - icon_class = 'doc-text' + 'doc-text' end end - - icon_class end private + def unknown_icon_sprite(icon_name) + known_sprites&.exclude?(icon_name) + end + + def unknown_file_icon_sprite(icon_name) + known_file_icon_sprites&.exclude?(icon_name) + end + def known_sprites return if Rails.env.production? @known_sprites ||= Gitlab::Json.parse(File.read(Rails.root.join('node_modules/@gitlab/svgs/dist/icons.json')))['icons'] end + def known_file_icon_sprites + return if Rails.env.production? + + @known_file_icon_sprites ||= Gitlab::Json.parse(File.read(Rails.root.join('node_modules/@gitlab/svgs/dist/file_icons/file_icons.json')))['icons'] + end + def memoized_icon(key) @rendered_icons ||= {} diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index 645a08bfcc0..a88be976337 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -277,11 +277,11 @@ module IntegrationsHelper s_("ProjectService|Trigger event for new comments.") when "confidential_note", "confidential_note_events" s_("ProjectService|Trigger event for new comments on confidential issues.") - when "issue", "issue_events" + when "issue", "issue_events", "issues_events" s_("ProjectService|Trigger event when an issue is created, updated, or closed.") - when "confidential_issue", "confidential_issue_events" + when "confidential_issue", "confidential_issue_events", "confidential_issues_events" s_("ProjectService|Trigger event when a confidential issue is created, updated, or closed.") - when "merge_request", "merge_request_events" + when "merge_request", "merge_request_events", "merge_requests_events" s_("ProjectService|Trigger event when a merge request is created, updated, or merged.") when "pipeline", "pipeline_events" s_("ProjectService|Trigger event when a pipeline status changes.") @@ -289,16 +289,20 @@ module IntegrationsHelper s_("ProjectService|Trigger event when a wiki page is created or updated.") when "commit", "commit_events" s_("ProjectService|Trigger event when a commit is created or updated.") - when "deployment" + when "deployment", "deployment_events" s_("ProjectService|Trigger event when a deployment starts or finishes.") - when "alert" + when "alert", "alert_events" s_("ProjectService|Trigger event when a new, unique alert is recorded.") - when "incident" + when "incident", "incident_events" s_("ProjectService|Trigger event when an incident is created.") when "group_mention" s_("ProjectService|Trigger event when a group is mentioned in a public context.") when "group_confidential_mention" s_("ProjectService|Trigger event when a group is mentioned in a confidential context.") + when "build_events" + s_("ProjectService|Trigger event when a build is created.") + when "archive_trace_events" + s_('When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces.') end end # rubocop:enable Metrics/CyclomaticComplexity @@ -323,12 +327,15 @@ module IntegrationsHelper def serialize_integration(integration, group: nil, project: nil) { - active: integration.operating?, + id: integration.id, + active: integration.activated?, + configured: integration.persisted?, title: integration.title, description: integration.description, updated_at: integration.updated_at, edit_path: scoped_edit_integration_path(integration, group: group, project: project), - name: integration.to_param + name: integration.to_param, + icon: integration.try(:avatar_url) } end end diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index 422380f3cc6..0443861903b 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -37,23 +37,13 @@ module InviteMembersHelper # Overridden in EE def common_invite_modal_dataset(source) - dataset = { + { id: source.id, root_id: source.root_ancestor&.id, name: source.name, default_access_level: Gitlab::Access::GUEST, full_path: source.full_path } - - if current_user && show_invite_members_for_task? - dataset.merge!( - tasks_to_be_done_options: tasks_to_be_done_options.to_json, - projects: projects_for_source(source).to_json, - new_project_path: source.is_a?(Group) ? new_project_path(namespace_id: source.id) : '' - ) - end - - dataset end private @@ -70,19 +60,6 @@ module InviteMembersHelper def users_filter_data(group) {} end - - def show_invite_members_for_task? - params[:open_modal] == 'invite_members_for_task' - end - - def tasks_to_be_done_options - ::MemberTask::TASKS.keys.map { |task| { value: task, text: localized_tasks_to_be_done_choices[task] } } - end - - def projects_for_source(source) - projects = source.is_a?(Project) ? [source] : source.projects - projects.map { |project| { id: project.id, title: project.title } } - end end InviteMembersHelper.prepend_mod_with('InviteMembersHelper') diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index c83545fa7a7..7f948db2f71 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -33,22 +33,6 @@ module IssuablesHelper end end - def sidebar_milestone_tooltip_label(milestone) - return _('Milestone') unless milestone.present? - - [escape_once(milestone[:title]), sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>') - end - - def sidebar_milestone_remaining_days(milestone) - due_date_with_remaining_days(milestone[:due_date], milestone[:start_date]) - end - - def due_date_with_remaining_days(due_date, start_date = nil) - return unless due_date - - "#{due_date.to_fs(:medium)} (#{remaining_days_in_words(due_date, start_date)})" - end - def multi_label_name(current_labels, default_label) return default_label if current_labels.blank? @@ -131,46 +115,6 @@ module IssuablesHelper end # rubocop: enable CodeReuse/ActiveRecord - def issuable_meta_author_status(author) - return "" unless author&.status&.customized? && status = user_status(author) - - 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.issue_type) - 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 - - if issuable.is_a?(Issue) && issuable.service_desk_reply_to - output << "#{html_escape(issuable.present(current_user: current_user).service_desk_reply_to)} via " - end - - output << content_tag(:strong) do - author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline-block") - author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-inline d-sm-none") - - author_output << issuable_meta_author_status(issuable.author) - - author_output - end - - if access = project.team.human_max_access(issuable.author_id) - output << content_tag(:span, access, class: "user-access-role has-tooltip d-none d-xl-inline-block gl-ml-3 ", title: _("This user has the %{access} role in the %{name} project.") % { access: access.downcase, name: project.name }) - elsif project.team.contributor?(issuable.author_id) - output << content_tag(:span, _("Contributor"), class: "user-access-role has-tooltip d-none d-xl-inline-block gl-ml-3", title: _("This user has previously committed to the %{name} project.") % { name: project.name }) - end - - output << content_tag(:span, (sprite_icon('first-contribution', css_class: 'gl-icon gl-vertical-align-middle') if issuable.first_contribution?), class: 'has-tooltip gl-ml-2', title: _('1st contribution!')) - - output.join.html_safe - end - def issuables_state_counter_text(issuable_type, state, display_count) titles = { opened: _("Open"), @@ -248,71 +192,6 @@ module IssuablesHelper data end - def issue_only_initial_data(issuable) - return {} unless issuable.is_a?(Issue) - - data = { - authorId: issuable.author.id, - authorName: issuable.author.name, - authorUsername: issuable.author.username, - authorWebUrl: url_for(user_path(issuable.author)), - createdAt: issuable.created_at.to_time.iso8601, - hasClosingMergeRequest: issuable.merge_requests_count(current_user) != 0, - isFirstContribution: issuable.first_contribution?, - issueType: issuable.issue_type, - serviceDeskReplyTo: issuable.present(current_user: current_user).service_desk_reply_to, - zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable), - sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord - iid: issuable.iid.to_s, - isHidden: issue_hidden?(issuable), - canCreateIncident: create_issue_type_allowed?(issuable.project, :incident), - **incident_only_initial_data(issuable) - } - - data.tap do |d| - if issuable.duplicated? && can?(current_user, :read_issue, issuable.duplicated_to) - d[:duplicatedToIssueUrl] = url_for([issuable.duplicated_to.project, issuable.duplicated_to, { only_path: false }]) - end - - if issuable.moved? && can?(current_user, :read_issue, issuable.moved_to) - d[:movedToIssueUrl] = url_for([issuable.moved_to.project, issuable.moved_to, { only_path: false }]) - end - end - end - - def incident_only_initial_data(issue) - return {} unless issue.incident_type_issue? - - { - hasLinkedAlerts: issue.alert_management_alerts.any?, - canUpdateTimelineEvent: can?(current_user, :admin_incident_management_timeline_event, issue), - currentPath: url_for(safe_params), - currentTab: safe_params[:incident_tab] - } - end - - def path_data(parent) - return { groupPath: parent.path } if parent.is_a?(Group) - - { - projectPath: ref_project.path, - projectId: ref_project.id, - projectNamespace: ref_project.namespace.full_path - } - end - - def updated_at_by(issuable) - return {} unless issuable.edited? - - { - updatedAt: issuable.last_edited_at.to_time.iso8601, - updatedBy: { - name: issuable.last_edited_by.name, - path: user_path(issuable.last_edited_by) - } - } - end - def issuables_count_for_state(issuable_type, state) Gitlab::IssuablesCountForState.new(finder, fast_fail: true, store_in_redis_cache: true)[state] end @@ -333,15 +212,6 @@ module IssuablesHelper issuable.author == current_user end - def issuable_display_type(issuable) - case issuable - when Issue - issuable.issue_type.downcase - when MergeRequest - issuable.model_name.human.downcase - end - end - def has_filter_bar_param? finder.class.scalar_params.any? { |p| params[p].present? } end @@ -353,12 +223,6 @@ module IssuablesHelper end end - def reviewer_sidebar_data(reviewer, merge_request: nil) - { avatar_url: reviewer.avatar_url, name: reviewer.name, username: reviewer.username }.tap do |data| - data[:can_merge] = merge_request.can_be_merged_by?(reviewer) if merge_request - end - end - def issuable_squash_option?(issuable, project) if issuable.persisted? issuable.squash @@ -428,27 +292,6 @@ module IssuablesHelper cookies[:collapsed_gutter] == 'true' end - def issuable_todo_button_data(issuable, is_collapsed) - { - todo_text: _('Add a to do'), - mark_text: _('Mark as done'), - todo_icon: sprite_icon('todo-add'), - mark_icon: sprite_icon('todo-done', css_class: 'todo-undone'), - issuable_id: issuable[:id], - issuable_type: issuable[:type], - create_path: issuable[:create_todo_path], - delete_path: issuable.dig(:current_user, :todo, :delete_path), - placement: is_collapsed ? 'left' : nil, - container: is_collapsed ? 'body' : nil, - boundary: 'viewport', - is_collapsed: is_collapsed, - track_label: "right_sidebar", - track_property: "update_todo", - track_action: "click_button", - track_value: "" - } - end - def close_reopen_params(issuable, action) { issuable.model_name.to_s.underscore => { state_event: action } @@ -520,6 +363,86 @@ module IssuablesHelper number_with_delimiter(count) end end + + def issue_only_initial_data(issuable) + return {} unless issuable.is_a?(Issue) + + { + canCreateIncident: create_issue_type_allowed?(issuable.project, :incident), + fullPath: issuable.project.full_path, + iid: issuable.iid, + issuableId: issuable.id, + issueType: issuable.issue_type, + isHidden: issue_hidden?(issuable), + sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord + zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable), + **incident_only_initial_data(issuable), + **issue_header_data(issuable), + **work_items_data + } + end + + def incident_only_initial_data(issue) + return {} unless issue.incident_type_issue? + + { + hasLinkedAlerts: issue.alert_management_alerts.any?, + canUpdateTimelineEvent: can?(current_user, :admin_incident_management_timeline_event, issue), + currentPath: url_for(safe_params), + currentTab: safe_params[:incident_tab] + } + end + + def issue_header_data(issuable) + data = { + authorId: issuable.author.id, + authorName: issuable.author.name, + authorUsername: issuable.author.username, + authorWebUrl: url_for(user_path(issuable.author)), + createdAt: issuable.created_at.to_time.iso8601, + isFirstContribution: issuable.first_contribution?, + serviceDeskReplyTo: issuable.present(current_user: current_user).service_desk_reply_to + } + + data.tap do |d| + if issuable.duplicated? && can?(current_user, :read_issue, issuable.duplicated_to) + d[:duplicatedToIssueUrl] = url_for([issuable.duplicated_to.project, issuable.duplicated_to, { only_path: false }]) + end + + if issuable.moved? && can?(current_user, :read_issue, issuable.moved_to) + d[:movedToIssueUrl] = url_for([issuable.moved_to.project, issuable.moved_to, { only_path: false }]) + end + end + end + + def work_items_data + { + registerPath: new_user_registration_path(redirect_to_referer: 'yes'), + signInPath: new_session_path(:user, redirect_to_referer: 'yes') + } + end + + def path_data(parent) + return { groupPath: parent.path } if parent.is_a?(Group) + + { + projectPath: ref_project.path, + projectId: ref_project.id, + projectNamespace: ref_project.namespace.full_path + } + end + + def updated_at_by(issuable) + return {} unless issuable.edited? + + { + updatedAt: issuable.last_edited_at.to_time.iso8601, + updatedBy: { + name: issuable.last_edited_by.name, + path: user_path(issuable.last_edited_by) + } + } + end end IssuablesHelper.prepend_mod_with('IssuablesHelper') diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index ed655b562c2..4419b573701 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -35,15 +35,6 @@ module IssuesHelper end end - def issue_status_visibility(issue, status_box:) - case status_box - when :open - 'hidden' if issue.closed? - when :closed - 'hidden' unless issue.closed? - end - end - def confidential_icon(issue) sprite_icon('eye-slash', css_class: 'gl-vertical-align-text-bottom') if issue.confidential? end @@ -128,24 +119,6 @@ module IssuesHelper can?(current_user, :create_merge_request_in, @project) end - def issue_closed_link(issue, current_user, css_class: '') - if issue.moved? && can?(current_user, :read_issue, issue.moved_to) - link_to(s_('IssuableStatus|moved'), issue.moved_to, class: css_class) - elsif issue.duplicated? && can?(current_user, :read_issue, issue.duplicated_to) - link_to(s_('IssuableStatus|duplicated'), issue.duplicated_to, class: css_class) - end - end - - def issue_closed_text(issue, current_user) - link = issue_closed_link(issue, current_user, css_class: 'text-underline gl-reset-color!') - - if link - s_('IssuableStatus|Closed (%{link})').html_safe % { link: link } - else - s_('IssuableStatus|Closed') - end - end - def show_moved_service_desk_issue_warning?(issue) return false unless issue.moved_from return false unless issue.from_service_desk? @@ -167,11 +140,8 @@ module IssuesHelper can_reopen_issue: can?(current_user, :reopen_issue, issuable).to_s, can_report_spam: issuable.submittable_as_spam_by?(current_user).to_s, can_update_issue: can?(current_user, :update_issue, issuable).to_s, - iid: issuable.iid, - issuable_id: issuable.id, is_issue_author: (issuable.author == current_user).to_s, issue_path: issuable_path(issuable), - issue_type: issuable_display_type(issuable), new_issue_path: new_project_issue_path(project, new_issuable_params), project_path: project.full_path, report_abuse_path: add_category_abuse_reports_path, diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 79bab0969d1..a6bc9bcf205 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -68,7 +68,7 @@ module LabelsHelper # We need the `label` argument here for EE def wrap_label_html(label_html, small:, label:) - wrapper_classes = %w(gl-label) + wrapper_classes = %w[gl-label] wrapper_classes << 'gl-label-sm' if small %(<span class="#{wrapper_classes.join(' ')}">#{label_html}</span>).html_safe @@ -220,10 +220,16 @@ module LabelsHelper project || group&.subgroup? end + def label_lock_on_merge_help_text + _('IMPORTANT: Use this setting only for VERY strict auditing purposes. ' \ + 'When turned on, nobody will be able to remove the label from any merge requests after they are merged. ' \ + 'In addition, nobody will be able to turn off this setting or delete this label.') + end + private def render_label_link(label_html, link:, title:, dataset:) - classes = %w(gl-link gl-label-link) + classes = %w[gl-link gl-label-link] dataset ||= {} if title.present? diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index 1a44f3554b0..1bd5cc41961 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -63,7 +63,7 @@ module MarkupHelper md = markdown_field(object, attribute, options.merge(post_process: false)) return unless md.present? - tags = %w(a gl-emoji b strong i em pre code p span) + tags = %w[a gl-emoji b strong i em pre code p span] context = markdown_field_render_context(object, attribute, options) context.reverse_merge!(truncate_visible_max_chars: max_chars || md.length) @@ -73,11 +73,11 @@ module MarkupHelper text, tags: tags, attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + - %w( + %w[ style data-src data-name data-unicode-version data-html data-reference-type data-project-path data-iid data-mr-title data-user - ) + ] ) render_links(text) diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index 42ffe338367..e4c1d7932aa 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -54,14 +54,6 @@ module MembersHelper end end - def localized_tasks_to_be_done_choices - { - code: s_('TasksToBeDone|Create/import code into a project (repository)'), - ci: s_('TasksToBeDone|Set up CI/CD pipelines to build, test, deploy, and monitor code'), - issues: s_('TasksToBeDone|Create/import issues (tickets) to collaborate on ideas and plan work') - }.freeze - end - private def source_text(member) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 32a183d6cd8..a90a16e120c 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -226,6 +226,13 @@ module MergeRequestsHelper } end + def mr_compare_form_data(_, merge_request) + { + source_branch_url: project_new_merge_request_branch_from_path(merge_request.source_project), + target_branch_url: project_new_merge_request_branch_to_path(merge_request.source_project) + } + end + private def review_requested_merge_requests_count @@ -269,7 +276,7 @@ module MergeRequestsHelper def merge_request_header(project, merge_request) link_to_author = link_to_member(project, merge_request.author, size: 24, extra_class: 'gl-font-weight-bold gl-mr-2', avatar: false) - copy_button = clipboard_button(text: merge_request.source_branch, title: _('Copy branch name'), class: 'btn btn-default btn-sm gl-button btn-default-tertiary btn-icon gl-display-none! gl-md-display-inline-block! js-source-branch-copy') + copy_button = clipboard_button(text: merge_request.source_branch, title: _('Copy branch name'), class: 'gl-display-none! gl-md-display-inline-block! js-source-branch-copy') target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mx-2' diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index 306c4d8694e..5274ace3d8a 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -157,7 +157,7 @@ module Nav partial: partial, component: 'invite_members', data: { - trigger_source: 'top-nav', + trigger_source: 'top_nav', trigger_element: 'text-emoji' } ) diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 4cbd5029ac9..d3707183964 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -79,11 +79,11 @@ module NavHelper end def admin_monitoring_nav_links - %w(system_info background_migrations background_jobs health_check) + %w[system_info background_migrations background_jobs health_check] end def admin_analytics_nav_links - %w(dev_ops_report usage_trends) + %w[dev_ops_report usage_trends] end def show_super_sidebar?(user = current_user) diff --git a/app/helpers/organizations/organization_helper.rb b/app/helpers/organizations/organization_helper.rb new file mode 100644 index 00000000000..6b5c4342c5c --- /dev/null +++ b/app/helpers/organizations/organization_helper.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Organizations + module OrganizationHelper + def organization_show_app_data(organization) + { + organization: organization.slice(:id, :name), + groups_and_projects_organization_path: groups_and_projects_organization_path(organization), + # TODO: Update counts to use real data + # https://gitlab.com/gitlab-org/gitlab/-/issues/424531 + association_counts: { + groups: 10, + projects: 5, + users: 1050 + } + }.merge(shared_groups_and_projects_app_data).to_json + end + + def organization_groups_and_projects_app_data + shared_groups_and_projects_app_data.to_json + end + + private + + def shared_groups_and_projects_app_data + { + projects_empty_state_svg_path: image_path('illustrations/empty-state/empty-projects-md.svg'), + groups_empty_state_svg_path: image_path('illustrations/empty-state/empty-groups-md.svg'), + new_group_path: new_group_path, + new_project_path: new_project_path + } + end + end +end diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index 05605394d57..8d260d5e455 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -34,7 +34,7 @@ module ProfilesHelper def middle_dot_divider_classes(stacking, breakpoint) ['gl-mb-3'].tap do |classes| if stacking - classes.concat(%w(middle-dot-divider-sm gl-display-block gl-sm-display-inline-block)) + classes.concat(%w[middle-dot-divider-sm gl-display-block gl-sm-display-inline-block]) else classes << 'gl-display-inline-block' classes << if breakpoint.nil? diff --git a/app/helpers/projects/observability_helper.rb b/app/helpers/projects/observability_helper.rb deleted file mode 100644 index 4515fdb1bc3..00000000000 --- a/app/helpers/projects/observability_helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Projects - module ObservabilityHelper - def observability_tracing_view_model(project) - Gitlab::Json.generate({ - tracingUrl: Gitlab::Observability.tracing_url(project), - provisioningUrl: Gitlab::Observability.provisioning_url(project), - oauthUrl: Gitlab::Observability.oauth_url - }) - end - - def observability_tracing_details_model(project, trace_id) - Gitlab::Json.generate({ - tracingIndexUrl: namespace_project_tracing_index_path(project.group, project), - traceId: trace_id, - tracingUrl: Gitlab::Observability.tracing_url(project), - provisioningUrl: Gitlab::Observability.provisioning_url(project), - oauthUrl: Gitlab::Observability.oauth_url - }) - end - end -end diff --git a/app/helpers/projects/pipeline_helper.rb b/app/helpers/projects/pipeline_helper.rb index 42e8e44c94c..0c3b7d26fe2 100644 --- a/app/helpers/projects/pipeline_helper.rb +++ b/app/helpers/projects/pipeline_helper.rb @@ -18,7 +18,7 @@ module Projects suite_endpoint: project_pipeline_test_path(project, pipeline, suite_name: 'suite', format: :json), blob_path: project_blob_path(project, pipeline.sha), has_test_report: pipeline.has_test_reports?, - empty_state_image_path: image_path('illustrations/empty-state/empty-test-cases-lg.svg'), + empty_state_image_path: image_path('illustrations/empty-todos-md.svg'), empty_dag_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'), artifacts_expired_image_path: image_path('illustrations/pipeline.svg'), tests_count: pipeline.test_report_summary.total[:count] diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 754e1b7c2a2..e45b38f2266 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -373,14 +373,6 @@ module ProjectsHelper false end - def metrics_external_dashboard_url - @project.metrics_setting_external_dashboard_url - end - - def metrics_dashboard_timezone - @project.metrics_setting_dashboard_timezone - end - def grafana_integration_url @project.grafana_integration&.grafana_url end diff --git a/app/helpers/registrations_helper.rb b/app/helpers/registrations_helper.rb index 4acba9b68d7..c2c142bca4d 100644 --- a/app/helpers/registrations_helper.rb +++ b/app/helpers/registrations_helper.rb @@ -7,7 +7,7 @@ module RegistrationsHelper min_length_message: s_('SignUp|Username is too short (minimum is %{min_length} characters).') % { min_length: User::MIN_USERNAME_LENGTH }, max_length: User::MAX_USERNAME_LENGTH, max_length_message: s_('SignUp|Username is too long (maximum is %{max_length} characters).') % { max_length: User::MAX_USERNAME_LENGTH }, - qa_selector: 'new_user_username_field' + testid: 'new-user-username-field' } end diff --git a/app/helpers/routing/projects_helper.rb b/app/helpers/routing/projects_helper.rb index 9b4aafe49b4..06de9022be4 100644 --- a/app/helpers/routing/projects_helper.rb +++ b/app/helpers/routing/projects_helper.rb @@ -43,12 +43,11 @@ module Routing end def work_item_url(entity, *args) - # TODO: we do not have a route to access group level work items yet. - # That is to be done as part of view group level work item issue: - # see https://gitlab.com/gitlab-org/gitlab/-/work_items/393987 - return unless entity.project.present? - - project_work_items_url(entity.project, entity.iid, *args) + if entity.project.present? + project_work_items_url(entity.project, entity.iid, *args) + else + group_work_item_url(entity.namespace, entity.iid, *args) + end end def merge_request_url(entity, *args) @@ -94,6 +93,8 @@ module Routing private def use_work_items_path?(issue) + return true if issue.project.blank? && issue.namespace.present? + issue.issue_type == 'task' end end diff --git a/app/helpers/safe_format_helper.rb b/app/helpers/safe_format_helper.rb index d39a972f3f3..71bfc9ecb40 100644 --- a/app/helpers/safe_format_helper.rb +++ b/app/helpers/safe_format_helper.rb @@ -36,7 +36,7 @@ module SafeFormatHelper # Returns an empty Hash if +tag+ is not a valid paired tag (e.g. <p>foo</p>). # an empty Hash is returned. # - # @param [String] tag is a HTML-safe output from tag helper + # @param [String] html_tag is a HTML-safe output from tag helper # @param [Symbol,Object] open_name name of opening tag # @param [Symbol,Object] close_name name of closing tag # @raise [ArgumentError] if +tag+ is not HTML-safe diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index cd32023adb6..f002a0c454d 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -242,7 +242,7 @@ module SearchHelper elsif current_controller?(:commits) 'commits' elsif current_controller?(:groups) - if %w(issues merge_requests).include?(controller.action_name) + if %w[issues merge_requests].include?(controller.action_name) controller.action_name end end @@ -479,7 +479,7 @@ module SearchHelper end.to_json end - def search_filter_input_options(type, placeholder = _('Search or filter results...')) + def search_filter_input_options(type, placeholder = _('Search or filter results…')) opts = { id: "filtered-search-#{type}", @@ -537,14 +537,14 @@ module SearchHelper source, count_tags: false, count_tail: false, - filtered_tags: %w(img), + filtered_tags: %w[img], max_length: 200 ) end def search_sanitize(html) # Truncato's filtered_tags and filtered_attributes are not quite the same - sanitize(html, tags: %w(a p ol ul li pre code)) + sanitize(html, tags: %w[a p ol ul li pre code]) end # _search_highlight is used in EE override diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 1bd7da0a352..33ca5ad584e 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -64,7 +64,8 @@ module SidebarsHelper gitlab_version: Gitlab.version_info, gitlab_version_check: gitlab_version_check, search: search_data, - panel_type: panel_type + panel_type: panel_type, + shortcut_links: shortcut_links } end @@ -106,7 +107,8 @@ module SidebarsHelper update_pins_url: pins_path, is_impersonating: impersonating?, stop_impersonation_path: admin_impersonation_path, - shortcut_links: shortcut_links(user, project: project) + shortcut_links: shortcut_links(user: user, project: project), + track_visits_path: track_namespace_visits_path }) end @@ -114,32 +116,43 @@ module SidebarsHelper nav: nil, project: nil, user: nil, group: nil, current_ref: nil, ref_type: nil, viewed_user: nil, organization: nil) context_adds = { route_is_active: method(:active_nav_link?), is_super_sidebar: true } - case nav - when 'project' - context = project_sidebar_context(project, user, current_ref, ref_type: ref_type, **context_adds) - Sidebars::Projects::SuperSidebarPanel.new(context) - when 'group' - context = group_sidebar_context(group, user, **context_adds) - Sidebars::Groups::SuperSidebarPanel.new(context) - when 'profile' - context = Sidebars::Context.new(current_user: user, container: user, **context_adds) - Sidebars::UserSettings::Panel.new(context) - when 'user_profile' - context = Sidebars::Context.new(current_user: user, container: viewed_user, **context_adds) - Sidebars::UserProfile::Panel.new(context) - when 'explore' - Sidebars::Explore::Panel.new(Sidebars::Context.new(current_user: user, container: nil, **context_adds)) - when 'search' - context = Sidebars::Context.new(current_user: user, container: nil, **context_adds) - Sidebars::Search::Panel.new(context) - when 'admin' - Sidebars::Admin::Panel.new(Sidebars::Context.new(current_user: user, container: nil, **context_adds)) - when 'organization' - context = organization_sidebar_context(organization, user, **context_adds) - Sidebars::Organizations::SuperSidebarPanel.new(context) - else + panel = case nav + when 'project' + context = project_sidebar_context(project, user, current_ref, ref_type: ref_type, **context_adds) + Sidebars::Projects::SuperSidebarPanel.new(context) + when 'group' + context = group_sidebar_context(group, user, **context_adds) + Sidebars::Groups::SuperSidebarPanel.new(context) + when 'profile' + context = Sidebars::Context.new(current_user: user, container: user, **context_adds) + Sidebars::UserSettings::Panel.new(context) + when 'user_profile' + context = Sidebars::Context.new(current_user: user, container: viewed_user, **context_adds) + Sidebars::UserProfile::Panel.new(context) + when 'explore' + Sidebars::Explore::Panel.new(Sidebars::Context.new(current_user: user, container: nil, **context_adds)) + when 'search' + context = Sidebars::Context.new(current_user: user, container: nil, **context_adds) + Sidebars::Search::Panel.new(context) + when 'admin' + Sidebars::Admin::Panel.new(Sidebars::Context.new(current_user: user, container: nil, **context_adds)) + when 'organization' + context = organization_sidebar_context(organization, user, **context_adds) + Sidebars::Organizations::SuperSidebarPanel.new(context) + when 'your_work' + context = your_work_sidebar_context(user, **context_adds) + Sidebars::YourWork::Panel.new(context) + end + + # We only return the panel if any menu item is rendered, otherwise fallback + return panel if panel&.render? + + # Fallback menu "Your work" for logged-in users, "Explore" for logged-out + if user context = your_work_sidebar_context(user, **context_adds) Sidebars::YourWork::Panel.new(context) + else + Sidebars::Explore::Panel.new(Sidebars::Context.new(current_user: nil, container: nil, **context_adds)) end end @@ -387,7 +400,29 @@ module SidebarsHelper !!session[:impersonator_id] end - def shortcut_links(user, project: nil) + def shortcut_links_anonymous + [ + { + title: _('Snippets'), + href: explore_snippets_path, + css_class: 'dashboard-shortcuts-snippets' + }, + { + title: _('Groups'), + href: explore_groups_path, + css_class: 'dashboard-shortcuts-groups' + }, + { + title: _('Projects'), + href: explore_projects_path, + css_class: 'dashboard-shortcuts-projects' + } + ] + end + + def shortcut_links(user: nil, project: nil) + return shortcut_links_anonymous unless user + shortcut_links = [ { title: _('Milestones'), @@ -403,6 +438,16 @@ module SidebarsHelper title: _('Activity'), href: activity_dashboard_path, css_class: 'dashboard-shortcuts-activity' + }, + { + title: _('Groups'), + href: dashboard_groups_path, + css_class: 'dashboard-shortcuts-groups' + }, + { + title: _('Projects'), + href: dashboard_projects_path, + css_class: 'dashboard-shortcuts-projects' } ] diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb index 07d83b8d850..21aa82aff1c 100644 --- a/app/helpers/sidekiq_helper.rb +++ b/app/helpers/sidekiq_helper.rb @@ -8,7 +8,7 @@ module SidekiqHelper (?<state>[DIEKNRSTVWXZLpsl\+<>/\d]+)\s+ (?<start>.+?)\s+ (?<command>(?:ruby\d+:\s+)?sidekiq.*\].*) - \z}x.freeze + \z}x def parse_sidekiq_ps(line) match = line.strip.match(SIDEKIQ_PS_REGEXP) diff --git a/app/helpers/stat_anchors_helper.rb b/app/helpers/stat_anchors_helper.rb index d9429f28be7..957985d6953 100644 --- a/app/helpers/stat_anchors_helper.rb +++ b/app/helpers/stat_anchors_helper.rb @@ -3,7 +3,7 @@ module StatAnchorsHelper def stat_anchor_attrs(anchor) {}.tap do |attrs| - attrs[:class] = %w(nav-link gl-display-flex gl-align-items-center) << extra_classes(anchor) + attrs[:class] = %w[nav-link gl-display-flex gl-align-items-center] << extra_classes(anchor) attrs[:itemprop] = anchor.itemprop if anchor.itemprop attrs[:data] = anchor.data if anchor.data end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 4f17634f3e4..0d885621b6c 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -272,9 +272,9 @@ module TodosHelper def show_todo_state?(todo) case todo.target when MergeRequest, Issue - %w(closed merged).include?(todo.target.state) + %w[closed merged].include?(todo.target.state) when AlertManagement::Alert - %i(resolved).include?(todo.target.state) + %i[resolved].include?(todo.target.state) else false end diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb index 12f78d9bd16..1b5d0b276a3 100644 --- a/app/helpers/users/callouts_helper.rb +++ b/app/helpers/users/callouts_helper.rb @@ -14,7 +14,6 @@ module Users PAGES_MOVED_CALLOUT = 'pages_moved_callout' REGISTRATION_ENABLED_CALLOUT_ALLOWED_CONTROLLER_PATHS = [/^root/, /^dashboard\S*/, /^admin\S*/].freeze WEB_HOOK_DISABLED = 'web_hook_disabled' - ULTIMATE_FEATURE_REMOVAL_BANNER = 'ultimate_feature_removal_banner' BRANCH_RULES_INFO_CALLOUT = 'branch_rules_info_callout' NEW_NAVIGATION_CALLOUT = 'new_navigation_callout' @@ -94,12 +93,6 @@ module Users Gitlab.com? && current_user.created_at >= Date.new(2023, 6, 2) end - def ultimate_feature_removal_banner_dismissed?(project) - return false unless project - - user_dismissed?(ULTIMATE_FEATURE_REMOVAL_BANNER, object: project) - end - private def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil, object: nil) diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index ac279904fd2..30f8f6fdfe5 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -164,7 +164,7 @@ module UsersHelper messageHtml: message, actionPrimary: { text: s_('AdminUsers|Confirm user'), - attributes: [{ variant: 'confirm', 'data-qa-selector': 'confirm_user_confirm_button' }] + attributes: [{ variant: 'confirm', 'data-testid': 'confirm-user-confirm-button' }] }, actionSecondary: { text: _('Cancel'), @@ -176,7 +176,7 @@ module UsersHelper path: confirm_admin_user_path(user), method: 'put', modal_attributes: modal_attributes, - qa_selector: 'confirm_user_button' + testid: 'confirm-user-button' } end diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index dc8ef4e44be..45a4b292eb5 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -5,9 +5,8 @@ module VersionCheckHelper def show_version_check? return false unless Gitlab::CurrentSettings.version_check_enabled - return false if User.single_user&.requires_usage_stats_consent? - current_user&.can_read_all_resources? + current_user&.can_read_all_resources? && !User.single_user&.requires_usage_stats_consent? end def gitlab_version_check diff --git a/app/helpers/vite_helper.rb b/app/helpers/vite_helper.rb new file mode 100644 index 00000000000..4d1085a5169 --- /dev/null +++ b/app/helpers/vite_helper.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ViteHelper + def universal_javascript_include_tag(*args) + if vite_enabled + vite_javascript_tag(*args) + else + javascript_include_tag(*args) + end + end + + def universal_asset_path(*args) + if vite_enabled + vite_asset_path(*args) + else + asset_path(*args) + end + end + + private + + def vite_enabled + Feature.enabled?(:vite) && !Rails.env.test? && vite_running + end + + def vite_running + ViteRuby.instance.dev_server_running? + end +end diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb index ba3c232bec4..92874168798 100644 --- a/app/helpers/webpack_helper.rb +++ b/app/helpers/webpack_helper.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module WebpackHelper + include ViteHelper + def prefetch_link_tag(source) href = asset_path(source) @@ -14,7 +16,11 @@ module WebpackHelper end def webpack_bundle_tag(bundle) - javascript_include_tag(*webpack_entrypoint_paths(bundle)) + if vite_running + vite_javascript_tag bundle + else + javascript_include_tag(*webpack_entrypoint_paths(bundle)) + end end def webpack_preload_asset_tag(asset, options = {}) @@ -32,6 +38,8 @@ module WebpackHelper end def webpack_controller_bundle_tags + return if Feature.enabled?(:vite) && !Rails.env.test? + chunks = [] action = case controller.action_name diff --git a/app/helpers/work_items_helper.rb b/app/helpers/work_items_helper.rb index 9036c7c8347..1969c98de8b 100644 --- a/app/helpers/work_items_helper.rb +++ b/app/helpers/work_items_helper.rb @@ -11,4 +11,10 @@ module WorkItemsHelper report_abuse_path: add_category_abuse_reports_path } end + + def work_items_list_data(group) + { + full_path: group.full_path + } + end end |