diff options
Diffstat (limited to 'app/helpers')
42 files changed, 466 insertions, 241 deletions
diff --git a/app/helpers/access_tokens_helper.rb b/app/helpers/access_tokens_helper.rb index 877ad6db576..1d38262159f 100644 --- a/app/helpers/access_tokens_helper.rb +++ b/app/helpers/access_tokens_helper.rb @@ -1,7 +1,30 @@ # frozen_string_literal: true module AccessTokensHelper + include AccountsHelper + include ApplicationHelper + def scope_description(prefix) prefix == :project_access_token ? [:doorkeeper, :project_access_token_scope_desc] : [:doorkeeper, :scope_desc] end + + def tokens_app_data + { + feed_token: { + enabled: !Gitlab::CurrentSettings.disable_feed_token, + token: current_user.feed_token, + reset_path: reset_feed_token_profile_path + }, + incoming_email_token: { + enabled: incoming_email_token_enabled?, + token: current_user.enabled_incoming_email_token, + reset_path: reset_incoming_email_token_profile_path + }, + static_object_token: { + enabled: static_objects_external_storage_enabled?, + token: current_user.enabled_static_object_token, + reset_path: reset_static_object_token_profile_path + } + }.to_json + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 58f933a7fe0..02a87979f40 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -206,10 +206,6 @@ module ApplicationHelper 'https://' + promo_host end - def contact_sales_url - promo_url + '/sales' - end - def support_url Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/' end diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 6fe92a5a978..c1a74382d46 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -86,6 +86,17 @@ module AuthHelper auth_providers.select { |provider| form_based_provider?(provider) } end + def saml_providers + auth_providers.select { |provider| auth_strategy_class(provider) == 'OmniAuth::Strategies::SAML' } + end + + def auth_strategy_class(provider) + config = Gitlab::Auth::OAuth::Provider.config_for(provider) + return if config.nil? || config['args'].blank? + + config.args['strategy_class'] + end + def any_form_based_providers_enabled? form_based_providers.any? { |provider| form_enabled_for_sign_in?(provider) } end @@ -164,10 +175,25 @@ module AuthHelper end def google_tag_manager_enabled? - Gitlab.com? && - extra_config.has_key?('google_tag_manager_id') && - extra_config.google_tag_manager_id.present? && - !current_user + return false unless Gitlab.dev_env_or_com? + + has_config_key = if Feature.enabled?(:gtm_nonce, type: :ops) + extra_config.has_key?('google_tag_manager_nonce_id') && + extra_config.google_tag_manager_nonce_id.present? + else + extra_config.has_key?('google_tag_manager_id') && + extra_config.google_tag_manager_id.present? + end + + has_config_key && !current_user + end + + def google_tag_manager_id + return unless google_tag_manager_enabled? + + return extra_config.google_tag_manager_nonce_id if Feature.enabled?(:gtm_nonce, type: :ops) + + extra_config.google_tag_manager_id end def auth_app_owner_text(owner) diff --git a/app/helpers/badges_helper.rb b/app/helpers/badges_helper.rb new file mode 100644 index 00000000000..a03f7f4097a --- /dev/null +++ b/app/helpers/badges_helper.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module BadgesHelper + VARIANT_CLASSES = { + muted: "badge-muted", + neutral: "badge-neutral", + info: "badge-info", + success: "badge-success", + warning: "badge-warning", + danger: "badge-danger" + }.tap { |hash| hash.default = hash.fetch(:muted) } .freeze + + SIZE_CLASSES = { + sm: "sm", + md: "md", + lg: "lg" + }.tap { |hash| hash.default = hash.fetch(:md) } .freeze + + GL_BADGE_CLASSES = %w[gl-badge badge badge-pill].freeze + + GL_ICON_CLASSES = %w[gl-icon gl-badge-icon].freeze + + # Creates a GitLab UI badge. + # + # Examples: + # # Plain text badge + # gl_badge_tag("foo") + # + # # Danger variant + # gl_badge_tag("foo", variant: :danger) + # + # # Small size + # gl_badge_tag("foo", size: :sm) + # + # # With icon + # gl_badge_tag("foo", icon: "question-o") + # + # # Icon-only + # gl_badge_tag("foo", icon: "question-o", icon_only: true) + # + # # Badge link + # gl_badge_tag("foo", nil, href: some_path) + # + # # Custom classes + # gl_badge_tag("foo", nil, class: "foo-bar") + # + # # Block content + # gl_badge_tag({ variant: :danger }, { class: "foo-bar" }) do + # "foo" + # end + # + # For accessibility, ensure that the given text or block is non-empty. + # + # See also https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-badge--default. + def gl_badge_tag(*args, &block) + if block_given? + build_gl_badge_tag(capture(&block), *args) + else + build_gl_badge_tag(*args) + end + end + + private + + def build_gl_badge_tag(content, options = nil, html_options = nil) + options ||= {} + html_options ||= {} + + icon_only = options[:icon_only] + variant_class = VARIANT_CLASSES[options.fetch(:variant, :muted)] + size_class = SIZE_CLASSES[options.fetch(:size, :md)] + + html_options = html_options.merge( + class: [ + *GL_BADGE_CLASSES, + variant_class, + size_class, + *html_options[:class] + ] + ) + + if icon_only + html_options['aria-label'] = content + html_options['role'] = 'img' + end + + if options[:icon] + icon_classes = GL_ICON_CLASSES.dup + icon_classes << "gl-mr-2" unless icon_only + icon = sprite_icon(options[:icon], css_class: icon_classes.join(' ')) + + content = icon_only ? icon : icon + content + end + + tag = html_options[:href].nil? ? :span : :a + + content_tag(tag, content, html_options) + end +end diff --git a/app/helpers/blame_helper.rb b/app/helpers/blame_helper.rb index 82c74e2416d..5117f7c6d9c 100644 --- a/app/helpers/blame_helper.rb +++ b/app/helpers/blame_helper.rb @@ -1,6 +1,13 @@ # frozen_string_literal: true module BlameHelper + BODY_FONT_SIZE = "0.875rem" + COMMIT_LINE_HEIGHT = 3 # 150% * 2 lines of text + COMMIT_PADDING = "10px" # 5px from both top and bottom + COMMIT_BLOCK_HEIGHT_EXP = "(#{BODY_FONT_SIZE} * #{COMMIT_LINE_HEIGHT}) + #{COMMIT_PADDING}" + CODE_LINE_HEIGHT = 1.1875 + CODE_PADDING = "20px" # 10px from both top and bottom + def age_map_duration(blame_groups, project) now = Time.zone.now start_date = blame_groups.map { |blame_group| blame_group[:commit].committed_date } @@ -24,4 +31,12 @@ module BlameHelper "blame-commit-age-#{age_group}" end end + + def intrinsic_row_css(line_count) + # using rems here because the size of the row depends on the text size + # which can be customized via user agent styles and browser preferences + total_line_height_exp = "#{line_count * CODE_LINE_HEIGHT}rem + #{CODE_PADDING}" + row_height_exp = line_count == 1 ? COMMIT_BLOCK_HEIGHT_EXP : total_line_height_exp + "contain-intrinsic-size: 1px calc(#{row_height_exp})" + end end diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index c26a73028b9..57da04b38cc 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -23,6 +23,7 @@ module BoardsHelper labels_filter_base_path: build_issue_link_base, labels_fetch_path: labels_fetch_path, labels_manage_path: labels_manage_path, + releases_fetch_path: releases_fetch_path, board_type: board.to_type } end @@ -65,6 +66,14 @@ module BoardsHelper end end + def releases_fetch_path + if board.group_board? + group_releases_path(@group) + else + project_releases_path(@project) + end + end + def board_base_url if board.group_board? group_boards_url(@group) diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb index d02fe3f20b0..c7f40decae8 100644 --- a/app/helpers/ci/jobs_helper.rb +++ b/app/helpers/ci/jobs_helper.rb @@ -19,6 +19,13 @@ module Ci } end + def bridge_data(build) + { + "build_name" => build.name, + "empty-state-illustration-path" => image_path('illustrations/job-trigger-md.svg') + } + end + def job_counts { "all" => limited_counter_with_delimiter(@all_builds), diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index 17057505173..8f219656b71 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -23,7 +23,7 @@ module Ci icon = 'status-paused' span_class = 'gl-text-gray-600' end - when :not_connected + when :not_connected, :never_contacted title = s_("Runners|New runner, has not connected yet") icon = 'warning-solid' when :offline diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index ca5fe38576e..2b5f726dad1 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -283,7 +283,7 @@ module DiffHelper return path unless path.size > max && max > 3 - "...#{path[-(max - 3)..-1]}" + "...#{path[-(max - 3)..]}" end def code_navigation_path(diffs) diff --git a/app/helpers/export_helper.rb b/app/helpers/export_helper.rb index 92d06471384..2699681fed7 100644 --- a/app/helpers/export_helper.rb +++ b/app/helpers/export_helper.rb @@ -18,7 +18,7 @@ module ExportHelper [ _('Milestones'), _('Labels'), - _('Boards and Board Lists'), + _('Boards and board lists'), _('Badges'), _('Subgroups') ] diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb index 9b4d0c0b9b3..3a5dcb4e664 100644 --- a/app/helpers/form_helper.rb +++ b/app/helpers/form_helper.rb @@ -35,7 +35,7 @@ module FormHelper def assignees_dropdown_options(issuable_type) dropdown_data = { toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data', - title: 'Select assignee', + title: _('Select assignee'), filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee', placeholder: _('Search users'), @@ -45,9 +45,9 @@ module FormHelper current_user: true, project_id: (@target_project || @project)&.id, field_name: "#{issuable_type}[assignee_ids][]", - default_label: 'Unassigned', + default_label: _('Unassigned'), 'max-select': 1, - 'dropdown-header': 'Assignee', + 'dropdown-header': _('Assignee'), multi_select: true, 'input-meta': 'name', 'always-show-selectbox': true, @@ -123,7 +123,7 @@ module FormHelper def multiple_assignees_dropdown_options(options) new_options = options.dup - new_options[:title] = 'Select assignee(s)' + new_options[:title] = _('Select assignee(s)') new_options[:data][:'dropdown-header'] = 'Assignee(s)' new_options[:data].delete(:'max-select') diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 09ff57e2baf..4d81aeca37a 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -29,7 +29,7 @@ module IdeHelper def convert_to_project_entity_json(project) return unless project - API::Entities::Project.represent(project).to_json + API::Entities::Project.represent(project, current_user: current_user).to_json end def enable_environments_guidance? diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index bb4a7fef6be..c5e767c6f64 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -17,31 +17,31 @@ module IntegrationsHelper "#{event}_events" end - def scoped_integrations_path - if @project.present? - project_settings_integrations_path(@project) - elsif @group.present? - group_settings_integrations_path(@group) + def scoped_integrations_path(project: nil, group: nil) + if project.present? + project_settings_integrations_path(project) + elsif group.present? + group_settings_integrations_path(group) else integrations_admin_application_settings_path end end - def scoped_integration_path(integration) - if @project.present? - project_service_path(@project, integration) - elsif @group.present? - group_settings_integration_path(@group, integration) + def scoped_integration_path(integration, project: nil, group: nil) + if project.present? + project_service_path(project, integration) + elsif group.present? + group_settings_integration_path(group, integration) else admin_application_settings_integration_path(integration) end end - def scoped_edit_integration_path(integration) - if @project.present? - edit_project_service_path(@project, integration) - elsif @group.present? - edit_group_settings_integration_path(@group, integration) + def scoped_edit_integration_path(integration, project: nil, group: nil) + if project.present? + edit_project_service_path(project, integration) + elsif group.present? + edit_group_settings_integration_path(group, integration) else edit_admin_application_settings_integration_path(integration) end @@ -51,11 +51,11 @@ module IntegrationsHelper overrides_admin_application_settings_integration_path(integration, options) end - def scoped_test_integration_path(integration) - if @project.present? - test_project_service_path(@project, integration) - elsif @group.present? - test_group_settings_integration_path(@group, integration) + def scoped_test_integration_path(integration, project: nil, group: nil) + if project.present? + test_project_service_path(project, integration) + elsif group.present? + test_group_settings_integration_path(group, integration) else test_admin_application_settings_integration_path(integration) end @@ -71,7 +71,7 @@ module IntegrationsHelper end end - def integration_form_data(integration, group: nil) + def integration_form_data(integration, project: nil, group: nil) form_data = { id: integration.id, show_active: integration.show_active_box?.to_s, @@ -87,9 +87,9 @@ module IntegrationsHelper inherit_from_id: integration.inherit_from_id, integration_level: integration_level(integration), editable: integration.editable?.to_s, - cancel_path: scoped_integrations_path, + cancel_path: scoped_integrations_path(project: project, group: group), can_test: integration.testable?.to_s, - test_path: scoped_test_integration_path(integration), + test_path: scoped_test_integration_path(integration, project: project, group: group), reset_path: scoped_reset_integration_path(integration, group: group) } @@ -107,9 +107,9 @@ module IntegrationsHelper } end - def integration_list_data(integrations) + def integration_list_data(integrations, group: nil, project: nil) { - integrations: integrations.map { |i| serialize_integration(i) }.to_json + integrations: integrations.map { |i| serialize_integration(i, group: group, project: project) }.to_json } end @@ -215,13 +215,13 @@ module IntegrationsHelper end end - def serialize_integration(integration) + def serialize_integration(integration, group: nil, project: nil) { active: integration.operating?, title: integration.title, description: integration.description, updated_at: integration.updated_at, - edit_path: scoped_edit_integration_path(integration), + edit_path: scoped_edit_integration_path(integration, group: group, project: project), name: integration.to_param } end diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index 01ae0ce4f31..8b26b646fdd 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -35,14 +35,7 @@ module InviteMembersHelper default_access_level: Gitlab::Access::GUEST } - experiment(:member_areas_of_focus, user: current_user) do |e| - e.publish_to_database - - e.control { dataset.merge!(areas_of_focus_options: [], no_selection_areas_of_focus: []) } - e.candidate { dataset.merge!(areas_of_focus_options: member_areas_of_focus_options.to_json, no_selection_areas_of_focus: ['no_selection']) } - end - - if show_invite_members_for_task? + if show_invite_members_for_task?(source) dataset.merge!( tasks_to_be_done_options: tasks_to_be_done_options.to_json, projects: projects_for_source(source).to_json, @@ -55,35 +48,16 @@ module InviteMembersHelper private - def member_areas_of_focus_options - [ - { - value: 'Contribute to the codebase', text: s_('InviteMembersModal|Contribute to the codebase') - }, - { - value: 'Collaborate on open issues and merge requests', text: s_('InviteMembersModal|Collaborate on open issues and merge requests') - }, - { - value: 'Configure CI/CD', text: s_('InviteMembersModal|Configure CI/CD') - }, - { - value: 'Configure security features', text: s_('InviteMembersModal|Configure security features') - }, - { - value: 'Other', text: s_('InviteMembersModal|Other') - } - ] - end - # Overridden in EE def users_filter_data(group) {} end - def show_invite_members_for_task? - return unless current_user && experiment(:invite_members_for_task).enabled? + def show_invite_members_for_task?(source) + return unless current_user - params[:open_modal] == 'invite_members_for_task' + invite_for_help_continuous_onboarding = source.is_a?(Project) && experiment(:invite_for_help_continuous_onboarding, namespace: source.namespace).variant.name == 'candidate' + params[:open_modal] == 'invite_members_for_task' || invite_for_help_continuous_onboarding end def tasks_to_be_done_options diff --git a/app/helpers/issuables_description_templates_helper.rb b/app/helpers/issuables_description_templates_helper.rb index 6b546d5c6fc..6c23f888823 100644 --- a/app/helpers/issuables_description_templates_helper.rb +++ b/app/helpers/issuables_description_templates_helper.rb @@ -6,7 +6,7 @@ module IssuablesDescriptionTemplatesHelper def template_dropdown_tag(issuable, &block) selected_template = selected_template(issuable) - title = selected_template || "Choose a template" + title = selected_template || _('Choose a template') options = { toggle_class: 'js-issuable-selector', title: title, diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 07f5adae272..53a7487741e 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -80,7 +80,7 @@ module IssuablesHelper def users_dropdown_label(selected_users) case selected_users.length when 0 - "Unassigned" + _('Unassigned') when 1 selected_users[0].name else @@ -133,7 +133,7 @@ module IssuablesHelper end # rubocop: enable CodeReuse/ActiveRecord - def milestone_dropdown_label(milestone_title, default_label = "Milestone") + def milestone_dropdown_label(milestone_title, default_label = _('Milestone')) title = case milestone_title when Milestone::Upcoming.name then Milestone::Upcoming.title @@ -188,7 +188,12 @@ module IssuablesHelper end def issuables_state_counter_text(issuable_type, state, display_count) - titles = { opened: "Open" } + titles = { + opened: _("Open"), + closed: _("Closed"), + merged: _("Merged"), + all: _("All") + } state_title = titles[state] || state.to_s.humanize html = content_tag(:span, state_title) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index a88ca6f6b11..cddf740a0e6 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -193,11 +193,13 @@ module IssuesHelper { can_create_issue: show_new_issue_link?(project).to_s, can_create_incident: create_issue_type_allowed?(project, :incident).to_s, + can_destroy_issue: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable).to_s, 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, 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, @@ -212,6 +214,8 @@ module IssuesHelper calendar_path: url_for(safe_params.merge(calendar_url_options)), empty_state_svg_path: image_path('illustrations/issues.svg'), full_path: namespace.full_path, + is_anonymous_search_disabled: Feature.enabled?(:disable_anonymous_search, type: :ops).to_s, + is_issue_repositioning_disabled: issue_repositioning_disabled?.to_s, is_signed_in: current_user.present?.to_s, jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), rss_path: url_for(safe_params.merge(rss_url_options)), diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb index 475469a6df9..9a0f0944fd1 100644 --- a/app/helpers/jira_connect_helper.rb +++ b/app/helpers/jira_connect_helper.rb @@ -8,7 +8,8 @@ module JiraConnectHelper groups_path: api_v4_groups_path(params: { min_access_level: Gitlab::Access::MAINTAINER, skip_groups: skip_groups }), subscriptions: subscriptions.map { |s| serialize_subscription(s) }.to_json, subscriptions_path: jira_connect_subscriptions_path, - users_path: current_user ? nil : jira_connect_users_path + users_path: current_user ? nil : jira_connect_users_path, # users_path is used to determine if user is signed in + gitlab_user_path: current_user ? user_path(current_user) : nil } end diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index 08a30c4d53b..7f8f6d77ff4 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -10,7 +10,8 @@ module LearnGitlabHelper def learn_gitlab_data(project) { actions: onboarding_actions_data(project).to_json, - sections: onboarding_sections_data.to_json + sections: onboarding_sections_data.to_json, + project: onboarding_project_data(project).to_json } end @@ -56,6 +57,10 @@ module LearnGitlabHelper } end + def onboarding_project_data(project) + { name: project.name } + end + def action_urls LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) } .merge(LearnGitlab::Onboarding::ACTION_DOC_URLS) diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index f185d6cd002..f16d9f6325b 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -181,7 +181,7 @@ module MarkupHelper wiki: wiki, repository: wiki.repository, page_slug: wiki_page.slug, - issuable_state_filter_enabled: true + issuable_reference_expansion_enabled: true ).merge(render_wiki_content_context_container(wiki)) end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index d5d692f2d6e..abb7128470f 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -182,7 +182,7 @@ module MergeRequestsHelper project_path: project_path(merge_request.project), changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'), is_fluid_layout: fluid_layout.to_s, - dismiss_endpoint: user_callouts_path, + dismiss_endpoint: callouts_path, show_suggest_popover: show_suggest_popover?.to_s, show_whitespace_default: @show_whitespace_default.to_s, file_by_file_default: @file_by_file_default.to_s, diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 106df168080..6acec417a75 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -88,6 +88,13 @@ module NamespacesHelper group.namespace_settings.public_send(method_name, **args) # rubocop:disable GitlabSecurity/PublicSend end + def namespaces_as_json(selected = :current_user) + { + group: formatted_namespaces(current_user.manageable_groups_with_routes), + user: formatted_namespaces([current_user.namespace]) + }.to_json + end + private # Many importers create a temporary Group, so use the real @@ -119,6 +126,17 @@ module NamespacesHelper [group_label.camelize, elements] end + + def formatted_namespaces(namespaces) + namespaces.sort_by(&:human_name).map! do |n| + { + id: n.id, + display_path: n.full_path, + human_name: n.human_name, + name: n.name + } + end + end end NamespacesHelper.prepend_mod_with('NamespacesHelper') diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index e7d69c38a54..715a5a02b50 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -50,7 +50,7 @@ module Nav menu_items.push(create_epic_menu_item(group)) - if Gitlab::Experimentation.active?(:invite_members_new_dropdown) && can?(current_user, :admin_group_member, group) + if can?(current_user, :admin_group_member, group) menu_items.push( invite_members_menu_item( href: group_group_members_path(group) @@ -101,7 +101,7 @@ module Nav ) end - if Gitlab::Experimentation.active?(:invite_members_new_dropdown) && can_admin_project_member?(project) + if can_admin_project_member?(project) menu_items.push( invite_members_menu_item( href: project_project_members_path(project) @@ -161,12 +161,11 @@ module Nav ::Gitlab::Nav::TopNavMenuItem.build( id: 'invite', title: s_('InviteMember|Invite members'), - emoji: ('shaking_hands' if experiment_enabled?(:invite_members_new_dropdown)), + emoji: 'shaking_hands', href: href, data: { - track_action: 'click_link', - track_label: tracking_label, - track_property: experiment_tracking_category_and_group(:invite_members_new_dropdown) + track_action: 'click_link_invite_members', + track_label: 'plus_menu_dropdown' } ) end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 9db28b54fe9..ddaef4652b4 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -67,7 +67,6 @@ module NotificationsHelper when :custom _('You will only receive notifications for the events you choose') when :owner_disabled - # Any change must be reflected in board_sidebar_subscription.vue _('Notifications have been disabled by the project or group owner') end end diff --git a/app/helpers/notify_helper.rb b/app/helpers/notify_helper.rb index ed96f3cef4f..c0ba93f4a30 100644 --- a/app/helpers/notify_helper.rb +++ b/app/helpers/notify_helper.rb @@ -20,21 +20,4 @@ module NotifyHelper (source.description || default_description).truncate(200, separator: ' ') end - - def invited_join_url(token, member) - additional_params = { invite_type: Emails::Members::INITIAL_INVITE } - - # order important below to our scheduled testing of these - # `from` experiment will be after the `text` on, but we may not cleanup - # from the `text` one by the time we run the `from` experiment, - # therefore we want to support `text` being fully enabled - # but if `from` is also enabled, then we only care about `from` - if experiment(:invite_email_from, actor: member).enabled? - additional_params[:experiment_name] = 'invite_email_from' - elsif experiment(:invite_email_preview_text, actor: member).enabled? - additional_params[:experiment_name] = 'invite_email_preview_text' - end - - invite_url(token, additional_params) - end end diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb index 5d2f225edcf..baeb9a477c3 100644 --- a/app/helpers/operations_helper.rb +++ b/app/helpers/operations_helper.rb @@ -16,7 +16,7 @@ module OperationsHelper { 'prometheus_activated' => prometheus_integration.manual_configuration?.to_s, - 'prometheus_form_path' => scoped_integration_path(prometheus_integration), + 'prometheus_form_path' => scoped_integration_path(prometheus_integration, project: prometheus_integration.project, group: prometheus_integration.group), 'prometheus_reset_key_path' => reset_alerting_token_project_settings_operations_path(@project), 'prometheus_authorization_key' => @project.alerting_setting&.token, 'prometheus_api_url' => prometheus_integration.api_url, diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index c69d9eb1326..66f80e7eeb8 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -38,17 +38,6 @@ module PackagesHelper "#{Gitlab.config.gitlab.host}/#{group_id}" end - def packages_list_data(type, resource) - { - resource_id: resource.id, - full_path: resource.full_path, - page_type: type, - empty_list_help_url: help_page_path('user/packages/package_registry/index'), - empty_list_illustration: image_path('illustrations/no-packages.svg'), - package_help_url: help_page_path('user/packages/index') - } - end - def track_package_event(event_name, scope, **args) ::Packages::CreateEventService.new(nil, current_user, event_name: event_name, scope: scope).execute category = args.delete(:category) || self.class.name diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index 09fc1ab9d50..0d514773891 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -61,6 +61,11 @@ module ProfilesHelper def ssh_key_expires_field_description s_('Profiles|Key can still be used after expiration.') end + + # Overridden in EE::ProfilesHelper#ssh_key_expiration_policy_enabled? + def ssh_key_expiration_policy_enabled? + false + end end ProfilesHelper.prepend_mod diff --git a/app/helpers/projects/cluster_agents_helper.rb b/app/helpers/projects/cluster_agents_helper.rb index 20fa721cc3b..aeeab250c7a 100644 --- a/app/helpers/projects/cluster_agents_helper.rb +++ b/app/helpers/projects/cluster_agents_helper.rb @@ -4,7 +4,8 @@ module Projects::ClusterAgentsHelper def js_cluster_agent_details_data(agent_name, project) { agent_name: agent_name, - project_path: project.full_path + project_path: project.full_path, + activity_empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg') } end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8366b25d2bc..827d2cb7164 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -120,6 +120,15 @@ module ProjectsHelper { project_full_name: project.full_name } end + def remove_fork_project_confirm_json(project, remove_form_id) + { + remove_form_id: remove_form_id, + button_text: _('Remove fork relationship'), + confirm_danger_message: remove_fork_project_warning_message(project), + phrase: @project.path + } + end + def visible_fork_source(project) project.fork_source if project.fork_source && can?(current_user, :read_project, project.fork_source) end @@ -405,6 +414,16 @@ module ProjectsHelper project.path_with_namespace end + def fork_button_disabled_tooltip(project) + return unless current_user + + if !current_user.can?(:fork_project, project) + s_("ProjectOverview|You don't have permission to fork this project") + elsif !current_user.can?(:create_fork) + s_('ProjectOverview|You have reached your project limit') + end + end + private def tab_ability_map diff --git a/app/helpers/routing/graphql_helper.rb b/app/helpers/routing/graphql_helper.rb index beefbb9b387..2e1d084e3cc 100644 --- a/app/helpers/routing/graphql_helper.rb +++ b/app/helpers/routing/graphql_helper.rb @@ -9,5 +9,9 @@ module Routing def graphql_etag_pipeline_sha_path(sha) [api_graphql_path, "pipelines/sha/#{sha}"].join(':') end + + def graphql_etag_project_on_demand_scan_counts_path(project) + [api_graphql_path, "on_demand_scan/counts/#{project.full_path}"].join(':') + end end end diff --git a/app/helpers/routing/pseudonymization_helper.rb b/app/helpers/routing/pseudonymization_helper.rb index ac30669dc83..fd9907edc37 100644 --- a/app/helpers/routing/pseudonymization_helper.rb +++ b/app/helpers/routing/pseudonymization_helper.rb @@ -3,7 +3,10 @@ module Routing module PseudonymizationHelper class MaskHelper - QUERY_PARAMS_TO_NOT_MASK = %w[].freeze + QUERY_PARAMS_TO_NOT_MASK = %w[ + scope + state + ].freeze def initialize(request_object, group, project) @request = request_object @@ -69,12 +72,10 @@ module Routing end end - def masked_page_url + def masked_page_url(group:, project:) return unless Feature.enabled?(:mask_page_urls, type: :ops) - current_group = group if defined?(group) - current_project = project if defined?(project) - mask_helper = MaskHelper.new(request, current_group, current_project) + mask_helper = MaskHelper.new(request, group, project) mask_helper.mask_params # We rescue all exception for time being till we test this helper extensively. diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index b28e5ff39b2..fb30e8ca059 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -70,6 +70,15 @@ module SortingHelper options end + def forks_sort_options_hash + { + sort_value_recently_created => sort_title_created_date, + sort_value_oldest_created => sort_title_created_date, + sort_value_latest_activity => sort_title_latest_activity, + sort_value_oldest_activity => sort_title_latest_activity + } + end + def projects_sort_option_titles # Only used for the project filter search bar projects_sort_options_hash.merge({ @@ -93,6 +102,15 @@ module SortingHelper } end + def forks_reverse_sort_options_hash + { + sort_value_recently_created => sort_value_oldest_created, + sort_value_oldest_created => sort_value_recently_created, + sort_value_latest_activity => sort_value_oldest_activity, + sort_value_oldest_activity => sort_value_latest_activity + } + end + def groups_sort_options_hash { sort_value_name => sort_title_name, @@ -303,6 +321,13 @@ module SortingHelper sort_direction_button(url, reverse_sort, sort_value) end + + def forks_sort_direction_button(sort_value, without = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]) + reverse_sort = forks_reverse_sort_options_hash[sort_value] + url = page_filter_path(sort: reverse_sort, without: without) + + sort_direction_button(url, reverse_sort, sort_value) + end end SortingHelper.prepend_mod_with('SortingHelper') diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 1d8b657025c..f2e1d158c2d 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -40,7 +40,9 @@ module SystemNoteHelper 'new_alert_added' => 'warning', 'severity' => 'information-o', 'cloned' => 'documents', - 'issue_type' => 'pencil-square' + 'issue_type' => 'pencil-square', + 'attention_requested' => 'user', + 'attention_request_removed' => 'user' }.freeze def system_note_icon_name(note) diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index e53e35baac3..2efc3f27dc7 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -14,8 +14,7 @@ module TabHelper gl_tabs_classes = %w[nav gl-tabs-nav] html_options = html_options.merge( - class: [*html_options[:class], gl_tabs_classes].join(' '), - role: 'tablist' + class: [*html_options[:class], gl_tabs_classes].join(' ') ) content = capture(&block) if block_given? @@ -54,7 +53,7 @@ module TabHelper extra_tab_classes = html_options.delete(:tab_class) tab_class = %w[nav-item].push(*extra_tab_classes) - content_tag(:li, class: tab_class, role: 'presentation') do + content_tag(:li, class: tab_class) do if block_given? link_to(options, html_options, &block) else @@ -63,6 +62,19 @@ module TabHelper end end + # Creates a <gl-badge> for use inside tabs. + # + # html_options - The html_options hash (default: {}) + def gl_tab_counter_badge(count, html_options = {}) + gl_badge_tag( + count, + { size: :sm }, + html_options.merge( + class: ['gl-tab-counter-badge', *html_options[:class]] + ) + ) + end + # Navigation link helper # # Returns an `li` element with an 'active' class if the supplied @@ -150,7 +162,7 @@ module TabHelper action = options.delete(:action) route_matches_paths?(options.delete(:path)) || - route_matches_pages?(options.delete(:page)) || + route_matches_page_without_exclusion?(options.delete(:page), options.delete(:exclude_page)) || route_matches_controllers_and_or_actions?(controller, action) end @@ -175,6 +187,13 @@ module TabHelper end end + def route_matches_page_without_exclusion?(pages, exclude_page) + return false unless route_matches_pages?(pages) + return true unless exclude_page.present? + + !route_matches_pages?(exclude_page) + end + def route_matches_pages?(pages) Array(pages).compact.any? do |single_page| # We need to distinguish between Hash argument and other types of @@ -211,12 +230,3 @@ module TabHelper current_page?(options) end end - -def gl_tab_counter_badge(count, html_options = {}) - badge_classes = %w[badge badge-muted badge-pill gl-badge sm gl-tab-counter-badge] - content_tag(:span, - count, - class: [*html_options[:class], badge_classes].join(' '), - data: html_options[:data] - ) -end diff --git a/app/helpers/time_zone_helper.rb b/app/helpers/time_zone_helper.rb index db355f5ff65..d16f13304e5 100644 --- a/app/helpers/time_zone_helper.rb +++ b/app/helpers/time_zone_helper.rb @@ -3,6 +3,7 @@ module TimeZoneHelper TIME_ZONE_FORMAT_ATTRS = { short: %i[identifier name offset], + abbr: %i[identifier abbr], full: %i[identifier name abbr offset formatted_offset] }.freeze private_constant :TIME_ZONE_FORMAT_ATTRS @@ -32,7 +33,7 @@ module TimeZoneHelper end end - def local_time_instance(timezone) + def local_timezone_instance(timezone) return Time.zone if timezone.blank? ActiveSupport::TimeZone.new(timezone) || Time.zone @@ -41,7 +42,7 @@ module TimeZoneHelper def local_time(timezone) return if timezone.blank? - time_zone_instance = local_time_instance(timezone) + time_zone_instance = local_timezone_instance(timezone) time_zone_instance.now.strftime("%-l:%M %p") end end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index d1f33f99ad0..d089b540282 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -81,7 +81,7 @@ module TreeHelper end def commit_in_fork_help - _("A new branch will be created in your fork and a new merge request will be started.") + _("GitLab will create a branch in your fork and start a merge request.") end def commit_in_single_accessible_branch diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb deleted file mode 100644 index d8e69145c40..00000000000 --- a/app/helpers/user_callouts_helper.rb +++ /dev/null @@ -1,98 +0,0 @@ -# frozen_string_literal: true - -module UserCalloutsHelper - GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration' - GCP_SIGNUP_OFFER = 'gcp_signup_offer' - SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed' - TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight' - CUSTOMIZE_HOMEPAGE = 'customize_homepage' - FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version' - REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout' - UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout' - INVITE_MEMBERS_BANNER = 'invite_members_banner' - SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout' - - def show_gke_cluster_integration_callout?(project) - active_nav_link?(controller: sidebar_operations_paths) && - can?(current_user, :create_cluster, project) && - !user_dismissed?(GKE_CLUSTER_INTEGRATION) - end - - def show_gcp_signup_offer? - !user_dismissed?(GCP_SIGNUP_OFFER) - end - - def render_flash_user_callout(flash_type, message, feature_name) - render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name - end - - def render_dashboard_ultimate_trial(user) - end - - def render_two_factor_auth_recovery_settings_check - end - - def show_suggest_popover? - !user_dismissed?(SUGGEST_POPOVER_DISMISSED) - end - - def show_customize_homepage_banner? - current_user.default_dashboard? && !user_dismissed?(CUSTOMIZE_HOMEPAGE) - end - - def show_feature_flags_new_version? - !user_dismissed?(FEATURE_FLAGS_NEW_VERSION) - end - - def show_unfinished_tag_cleanup_callout? - !user_dismissed?(UNFINISHED_TAG_CLEANUP_CALLOUT) - end - - def show_registration_enabled_user_callout? - !Gitlab.com? && - current_user&.admin? && - signup_enabled? && - !user_dismissed?(REGISTRATION_ENABLED_CALLOUT) - end - - def dismiss_two_factor_auth_recovery_settings_check - end - - def show_invite_banner?(group) - Ability.allowed?(current_user, :admin_group, group) && - !just_created? && - !user_dismissed_for_group(INVITE_MEMBERS_BANNER, group) && - !multiple_members?(group) - end - - def show_security_newsletter_user_callout? - current_user&.admin? && - !user_dismissed?(SECURITY_NEWSLETTER_CALLOUT) - end - - private - - def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil) - return false unless current_user - - current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than) - end - - def user_dismissed_for_group(feature_name, group, ignore_dismissal_earlier_than = nil) - return false unless current_user - - current_user.dismissed_callout_for_group?(feature_name: feature_name, - group: group, - ignore_dismissal_earlier_than: ignore_dismissal_earlier_than) - end - - def just_created? - flash[:notice]&.include?('successfully created') - end - - def multiple_members?(group) - group.member_count > 1 || group.members_with_parents.count > 1 - end -end - -UserCalloutsHelper.prepend_mod diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb new file mode 100644 index 00000000000..32b0d7b3fe3 --- /dev/null +++ b/app/helpers/users/callouts_helper.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Users + module CalloutsHelper + GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration' + GCP_SIGNUP_OFFER = 'gcp_signup_offer' + SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed' + TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight' + FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version' + REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout' + UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout' + SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout' + + def show_gke_cluster_integration_callout?(project) + active_nav_link?(controller: sidebar_operations_paths) && + can?(current_user, :create_cluster, project) && + !user_dismissed?(GKE_CLUSTER_INTEGRATION) + end + + def show_gcp_signup_offer? + !user_dismissed?(GCP_SIGNUP_OFFER) + end + + def render_flash_user_callout(flash_type, message, feature_name) + render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name + end + + def render_dashboard_ultimate_trial(user) + end + + def render_two_factor_auth_recovery_settings_check + end + + def show_suggest_popover? + !user_dismissed?(SUGGEST_POPOVER_DISMISSED) + end + + def show_feature_flags_new_version? + !user_dismissed?(FEATURE_FLAGS_NEW_VERSION) + end + + def show_unfinished_tag_cleanup_callout? + !user_dismissed?(UNFINISHED_TAG_CLEANUP_CALLOUT) + end + + def show_registration_enabled_user_callout? + !Gitlab.com? && + current_user&.admin? && + signup_enabled? && + !user_dismissed?(REGISTRATION_ENABLED_CALLOUT) + end + + def dismiss_two_factor_auth_recovery_settings_check + end + + def show_security_newsletter_user_callout? + current_user&.admin? && + !user_dismissed?(SECURITY_NEWSLETTER_CALLOUT) + end + + private + + def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil) + return false unless current_user + + current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than) + end + end +end + +Users::CalloutsHelper.prepend_mod diff --git a/app/helpers/users/group_callouts_helper.rb b/app/helpers/users/group_callouts_helper.rb new file mode 100644 index 00000000000..b66c7f9f821 --- /dev/null +++ b/app/helpers/users/group_callouts_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Users + module GroupCalloutsHelper + INVITE_MEMBERS_BANNER = 'invite_members_banner' + + def show_invite_banner?(group) + Ability.allowed?(current_user, :admin_group, group) && + !just_created? && + !user_dismissed_for_group(INVITE_MEMBERS_BANNER, group) && + !multiple_members?(group) + end + + private + + def user_dismissed_for_group(feature_name, group, ignore_dismissal_earlier_than = nil) + return false unless current_user + + current_user.dismissed_callout_for_group?(feature_name: feature_name, + group: group, + ignore_dismissal_earlier_than: ignore_dismissal_earlier_than) + end + + def just_created? + flash[:notice]&.include?('successfully created') + end + + def multiple_members?(group) + group.member_count > 1 || group.members_with_parents.count > 1 + end + end +end diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index f8d7264d4cc..7875b9e4a28 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -6,7 +6,7 @@ module VersionCheckHelper return unless Gitlab::CurrentSettings.version_check_enabled return if User.single_user&.requires_usage_stats_consent? - image_tag VersionCheck.url, class: 'js-version-status-badge' + image_tag VersionCheck.image_url, class: 'js-version-status-badge' end def link_to_version diff --git a/app/helpers/x509_helper.rb b/app/helpers/x509_helper.rb index 4afc5643af4..1a9dbefceef 100644 --- a/app/helpers/x509_helper.rb +++ b/app/helpers/x509_helper.rb @@ -18,6 +18,6 @@ module X509Helper end def x509_signature?(sig) - sig.is_a?(X509CommitSignature) || sig.is_a?(Gitlab::X509::Signature) + sig.is_a?(CommitSignatures::X509CommitSignature) || sig.is_a?(Gitlab::X509::Signature) end end |