diff options
Diffstat (limited to 'app/helpers')
28 files changed, 341 insertions, 157 deletions
diff --git a/app/helpers/analytics/cycle_analytics_helper.rb b/app/helpers/analytics/cycle_analytics_helper.rb index c43ac545bf8..35a5d4f469d 100644 --- a/app/helpers/analytics/cycle_analytics_helper.rb +++ b/app/helpers/analytics/cycle_analytics_helper.rb @@ -7,5 +7,23 @@ module Analytics Analytics::CycleAnalytics::StagePresenter.new(stage_params) end end + + def cycle_analytics_initial_data(project, group = nil) + base_data = { project_id: project.id, group_path: project.group&.path, request_path: project_cycle_analytics_path(project), full_path: project.full_path } + svgs = { empty_state_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_data_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_access_svg_path: image_path("illustrations/analytics/no-access.svg") } + api_paths = group.present? ? cycle_analytics_group_api_paths(group) : cycle_analytics_project_api_paths(project) + + base_data.merge(svgs, api_paths) + end + + private + + def cycle_analytics_group_api_paths(group) + { milestones_path: group_milestones_path(group, format: :json), labels_path: group_labels_path(group, format: :json), group_path: group_path(group), group_id: group&.id } + end + + def cycle_analytics_project_api_paths(project) + { milestones_path: project_milestones_path(project, format: :json), labels_path: project_labels_path(project, format: :json), group_path: project.parent&.path, group_id: project.parent&.id } + end end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 2447a731167..cf15433f2e5 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -176,6 +176,16 @@ module ApplicationSettingsHelper "and the value is encrypted at rest.") end + def sidekiq_job_limiter_mode_help_text + _("How the job limiter handles jobs exceeding the thresholds specified below. "\ + "The 'track' mode only logs the jobs. The 'compress' mode compresses the jobs and "\ + "raises an exception if the compressed size exceeds the limit.") + end + + def sidekiq_job_limiter_modes_for_select + ApplicationSetting.sidekiq_job_limiter_modes.keys.map { |mode| [mode.humanize, mode] } + end + def visible_attributes [ :abuse_notification_email, @@ -263,6 +273,8 @@ module ApplicationSettingsHelper :max_attachment_size, :max_import_size, :max_pages_size, + :max_yaml_size_bytes, + :max_yaml_depth, :metrics_method_call_threshold, :minimum_password_length, :mirror_available, @@ -309,18 +321,30 @@ module ApplicationSettingsHelper :throttle_authenticated_api_enabled, :throttle_authenticated_api_period_in_seconds, :throttle_authenticated_api_requests_per_period, + :throttle_authenticated_git_lfs_enabled, + :throttle_authenticated_git_lfs_period_in_seconds, + :throttle_authenticated_git_lfs_requests_per_period, :throttle_authenticated_web_enabled, :throttle_authenticated_web_period_in_seconds, :throttle_authenticated_web_requests_per_period, :throttle_authenticated_packages_api_enabled, :throttle_authenticated_packages_api_period_in_seconds, :throttle_authenticated_packages_api_requests_per_period, + :throttle_authenticated_files_api_enabled, + :throttle_authenticated_files_api_period_in_seconds, + :throttle_authenticated_files_api_requests_per_period, + :throttle_unauthenticated_api_enabled, + :throttle_unauthenticated_api_period_in_seconds, + :throttle_unauthenticated_api_requests_per_period, :throttle_unauthenticated_enabled, :throttle_unauthenticated_period_in_seconds, :throttle_unauthenticated_requests_per_period, :throttle_unauthenticated_packages_api_enabled, :throttle_unauthenticated_packages_api_period_in_seconds, :throttle_unauthenticated_packages_api_requests_per_period, + :throttle_unauthenticated_files_api_enabled, + :throttle_unauthenticated_files_api_period_in_seconds, + :throttle_unauthenticated_files_api_requests_per_period, :throttle_protected_paths_enabled, :throttle_protected_paths_period_in_seconds, :throttle_protected_paths_requests_per_period, @@ -372,7 +396,11 @@ module ApplicationSettingsHelper :container_registry_expiration_policies_worker_capacity, :container_registry_cleanup_tags_service_max_list_size, :keep_latest_artifact, - :whats_new_variant + :whats_new_variant, + :user_deactivation_emails_enabled, + :sidekiq_job_limiter_mode, + :sidekiq_job_limiter_compression_threshold_bytes, + :sidekiq_job_limiter_limit_bytes ].tap do |settings| settings << :deactivate_dormant_users unless Gitlab.com? end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index c1a33794b50..f0e8ff7778e 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -183,6 +183,10 @@ module BlobHelper blob_raw_url(**kwargs, only_path: true) end + def parent_dir_raw_path + blob_raw_path.rpartition("/").first + "/" + end + # SVGs can contain malicious JavaScript; only include whitelisted # elements and attributes. Note that this whitelist is by no means complete # and may omit some elements. diff --git a/app/helpers/ci/pipeline_editor_helper.rb b/app/helpers/ci/pipeline_editor_helper.rb index 4dfe136c206..9bbc326a750 100644 --- a/app/helpers/ci/pipeline_editor_helper.rb +++ b/app/helpers/ci/pipeline_editor_helper.rb @@ -16,7 +16,6 @@ module Ci "ci-config-path": project.ci_config_path_or_default, "ci-examples-help-page-path" => help_page_path('ci/examples/index'), "ci-help-page-path" => help_page_path('ci/index'), - "commit-sha" => commit_sha, "default-branch" => project.default_branch_or_main, "empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'), "initial-branch-name" => initial_branch, diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index 550fa4de2c5..c9231a4eff3 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -65,6 +65,15 @@ module Ci } end + def group_runners_data_attributes(group) + { + registration_token: group.runners_token, + group_id: group.id, + group_full_path: group.full_path, + runner_install_help_page: 'https://docs.gitlab.com/runner/install/' + } + end + def toggle_shared_runners_settings_data(project) { is_enabled: "#{project.shared_runners_enabled?}", diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 40d7eab584c..ca5fe38576e 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -183,9 +183,9 @@ module DiffHelper def diff_file_changed_icon_color(diff_file) if diff_file.deleted_file? - "cred" + "danger" elsif diff_file.new_file? - "cgreen" + "success" end end @@ -248,6 +248,23 @@ module DiffHelper toggle_whitespace_link(url, options) end + def diff_files_data(diff_files) + diffs_map = diff_files.map do |f| + { + href: "##{hexdigest(f.file_path)}", + title: f.new_path, + name: f.file_path, + path: diff_file_path_text(f), + icon: diff_file_changed_icon(f), + iconColor: "#{diff_file_changed_icon_color(f)}", + added: f.added_lines, + removed: f.removed_lines + } + end + + diffs_map.to_json + end + def hide_whitespace? params[:w] == '1' end diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 3f23f73eed7..f57bb600527 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -73,7 +73,6 @@ module EnvironmentHelper external_url: environment.external_url, can_update_environment: can?(current_user, :update_environment, environment), can_destroy_environment: can_destroy_environment?(environment), - can_read_environment: can?(current_user, :read_environment, environment), can_stop_environment: can?(current_user, :stop_environment, environment), can_admin_environment: can?(current_user, :admin_environment, project), environment_metrics_path: environment_metrics_path(environment), diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 0f835e6881e..1be395437ea 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -16,6 +16,7 @@ module GitlabRoutingHelper include ::Routing::SnippetsHelper include ::Routing::WikiHelper include ::Routing::GraphqlHelper + include ::Routing::PseudonymizationHelper included do Gitlab::Routing.includes_helpers(self) end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 0e4aeaae20d..a24776eb2e4 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,14 +1,6 @@ # frozen_string_literal: true module GroupsHelper - def group_sidebar_links - @group_sidebar_links ||= get_group_sidebar_links - end - - def group_sidebar_link?(link) - group_sidebar_links.include?(link) - end - def can_change_group_visibility_level?(group) can?(current_user, :change_visibility_level, group) end @@ -33,29 +25,6 @@ module GroupsHelper Ability.allowed?(current_user, :admin_group_member, group) end - def group_issues_count(state:) - IssuesFinder - .new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true) - .execute - .count - end - - def group_merge_requests_count(state:) - MergeRequestsFinder - .new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true) - .execute - .count - end - - def group_dependency_proxy_image_prefix(group) - # The namespace path can include uppercase letters, which - # Docker doesn't allow. The proxy expects it to be downcased. - url = "#{group_url(group).downcase}#{DependencyProxy::URL_SUFFIX}" - - # Docker images do not include the protocol - url.partition('//').last - end - def group_icon_url(group, options = {}) if group.is_a?(String) group = Group.find_by_full_path(group) @@ -153,12 +122,6 @@ module GroupsHelper groups.to_json end - def show_invite_banner?(group) - can?(current_user, :admin_group, group) && - !just_created? && - !multiple_members?(group) - end - def render_setting_to_allow_project_access_token_creation?(group) group.root? && current_user.can?(:admin_setting_to_allow_project_access_token_creation, group) end @@ -173,44 +136,6 @@ module GroupsHelper private - def just_created? - flash[:notice] =~ /successfully created/ - end - - def multiple_members?(group) - group.member_count > 1 || group.members_with_parents.count > 1 - end - - def get_group_sidebar_links - links = [:overview, :group_members] - - resources = [:activity, :issues, :boards, :labels, :milestones, - :merge_requests] - links += resources.select do |resource| - can?(current_user, "read_group_#{resource}".to_sym, @group) - end - - # TODO Proper policies, such as `read_group_runners, should be implemented per - # See https://gitlab.com/gitlab-org/gitlab/-/issues/334802 - if can?(current_user, :admin_group, @group) && Feature.enabled?(:runner_list_group_view_vue_ui, @group, default_enabled: :yaml) - links << :runners - end - - if can?(current_user, :read_cluster, @group) - links << :kubernetes - end - - if can?(current_user, :admin_group, @group) - links << :settings - end - - if can?(current_user, :read_wiki, @group) - links << :wiki - end - - links - end - def group_title_link(group, hidable: false, show_avatar: false, for_dropdown: false) link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do icon = group_icon(group, class: "avatar-tile", width: 15, height: 15) if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? @@ -271,6 +196,18 @@ module GroupsHelper def group_url_error_message s_('GroupSettings|Please choose a group URL with no special characters or spaces.') end + + # Maps `jobs_to_be_done` values to option texts + def localized_jobs_to_be_done_choices + { + basics: _('I want to learn the basics of Git'), + move_repository: _('I want to move my repository to GitLab from somewhere else'), + code_storage: _('I want to store my code'), + exploring: _('I want to explore GitLab to see if it’s worth switching to'), + ci: _('I want to use GitLab CI with my existing repository'), + other: _('A different reason') + }.with_indifferent_access.freeze + end end GroupsHelper.prepend_mod_with('GroupsHelper') diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index 5134b484249..d9bd64f4c2e 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -9,14 +9,6 @@ module InviteMembersHelper Feature.enabled?(:invite_members_group_modal, project.group) && can?(current_user, :admin_project_member, project) end - def can_invite_group_for_project?(project) - # do not use the can_admin_project_member? helper here due to structure of the view and how membership_locked? - # is leveraged for inviting groups - Feature.enabled?(:invite_members_group_modal, project.group) && - can?(current_user, :admin_project_member, project) && - project.allowed_to_share_with_group? - end - def invite_accepted_notice(member) case member.source when Project diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index d8ba530f3f6..f3cc46216e5 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -3,6 +3,7 @@ module IssuablesHelper include GitlabRoutingHelper include IssuablesDescriptionTemplatesHelper + include ::Sidebars::Concerns::HasPill def sidebar_gutter_toggle_icon content_tag(:span, class: 'js-sidebar-toggle-container', data: { is_expanded: !sidebar_gutter_collapsed? }) do @@ -187,19 +188,18 @@ module IssuablesHelper end def issuables_state_counter_text(issuable_type, state, display_count) - titles = { - opened: "Open" - } - + titles = { opened: "Open" } state_title = titles[state] || state.to_s.humanize html = content_tag(:span, state_title) return html.html_safe unless display_count count = issuables_count_for_state(issuable_type, state) - if count != -1 - html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge badge-muted badge-pill gl-badge gl-tab-counter-badge sm') + html << " " << content_tag(:span, + format_count(issuable_type, count, Gitlab::IssuablesCountForState::THRESHOLD), + class: 'badge badge-muted badge-pill gl-badge gl-tab-counter-badge sm' + ) end html.html_safe @@ -256,7 +256,8 @@ module IssuablesHelper issueType: issuable.issue_type, zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable), sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord - iid: issuable.iid.to_s + iid: issuable.iid.to_s, + isHidden: issue_hidden?(issuable) } end @@ -283,7 +284,9 @@ module IssuablesHelper end def issuables_count_for_state(issuable_type, state) - Gitlab::IssuablesCountForState.new(finder)[state] + store_in_cache = parent.is_a?(Group) ? parent.cached_issues_state_count_enabled? : false + + Gitlab::IssuablesCountForState.new(finder, store_in_redis_cache: store_in_cache)[state] end def close_issuable_path(issuable) @@ -370,7 +373,7 @@ module IssuablesHelper is_collapsed: is_collapsed, track_label: "right_sidebar", track_property: "update_todo", - track_event: "click_button", + track_action: "click_button", track_value: "" } end @@ -437,6 +440,14 @@ module IssuablesHelper def parent @project || @group end + + def format_count(issuable_type, count, threshold) + if issuable_type == :issues && parent.is_a?(Group) && parent.cached_issues_state_count_enabled? + format_cached_count(threshold, count) + else + number_with_delimiter(count) + end + end end IssuablesHelper.prepend_mod_with('IssuablesHelper') diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index bbafdac9a7f..40e86b4623c 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -60,8 +60,16 @@ module IssuesHelper sprite_icon('eye-slash', css_class: 'gl-vertical-align-text-bottom') if issue.confidential? end + def issue_hidden?(issue) + Feature.enabled?(:ban_user_feature_flag) && issue.hidden? + end + def hidden_issue_icon(issue) - sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom') if issue.hidden? + return unless issue_hidden?(issue) + + content_tag(:span, class: 'has-tooltip', title: _('This issue is hidden because its author has been banned')) do + sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom') + end end def award_user_list(awards, current_user, limit: 10) @@ -174,7 +182,11 @@ module IssuesHelper end def issue_header_actions_data(project, issuable, current_user) - new_issuable_params = ({ issuable_template: 'incident', issue: { issue_type: 'incident' } } if issuable.incident?) + new_issuable_params = { issue: { description: _('Related to #%{issue_id}.') % { issue_id: issuable.iid } + "\n\n" } } + if issuable.incident? + new_issuable_params[:issuable_template] = 'incident' + new_issuable_params[:issue][:issue_type] = 'incident' + end { can_create_issue: show_new_issue_link?(project).to_s, @@ -191,34 +203,45 @@ module IssuesHelper } end - def issues_list_data(project, current_user, finder) + def common_issues_list_data(namespace, current_user) { autocomplete_award_emojis_path: autocomplete_award_emojis_path, 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_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)), + sign_in_path: new_user_session_path + } + end + + def project_issues_list_data(project, current_user, finder) + common_issues_list_data(project, current_user).merge( can_bulk_update: can?(current_user, :admin_issue, project).to_s, can_edit: can?(current_user, :admin_project, project).to_s, can_import_issues: can?(current_user, :import_issues, @project).to_s, - email: current_user&.notification_email, + email: current_user&.notification_email_or_default, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), - empty_state_svg_path: image_path('illustrations/issues.svg'), export_csv_path: export_csv_project_issues_path(project), - has_project_issues: project_issues(project).exists?.to_s, + has_any_issues: project_issues(project).exists?.to_s, import_csv_issues_path: import_csv_namespace_project_issues_path, initial_email: project.new_issuable_address(current_user, 'issue'), - is_signed_in: current_user.present?.to_s, - issues_path: project_issues_path(project), - jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), + is_project: true.to_s, markdown_help_path: help_page_path('user/markdown'), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }), project_import_jira_path: project_import_jira_path(project), - project_path: project.full_path, quick_actions_help_path: help_page_path('user/project/quick_actions'), reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'), - rss_path: url_for(safe_params.merge(rss_url_options)), - show_new_issue_link: show_new_issue_link?(project).to_s, - sign_in_path: new_user_session_path - } + show_new_issue_link: show_new_issue_link?(project).to_s + ) + end + + def group_issues_list_data(group, current_user, issues) + common_issues_list_data(group, current_user).merge( + has_any_issues: issues.to_a.any?.to_s + ) end # Overridden in EE diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index a3a8a275f67..4fb7a05a0e9 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -1,23 +1,12 @@ # frozen_string_literal: true module LearnGitlabHelper - def learn_gitlab_experiment_enabled?(project) + def learn_gitlab_enabled?(project) return false unless current_user - return false unless continous_onboarding_experiment_enabled_for_user? learn_gitlab_onboarding_available?(project) end - def learn_gitlab_experiment_tracking_category - return unless current_user - - if Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_a, subject: current_user) - Gitlab::Experimentation.get_experiment(:learn_gitlab_a).tracking_category - elsif Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_b, subject: current_user) - Gitlab::Experimentation.get_experiment(:learn_gitlab_b).tracking_category - end - end - def onboarding_actions_data(project) attributes = onboarding_progress(project).attributes.symbolize_keys @@ -31,11 +20,6 @@ module LearnGitlabHelper end end - def continous_onboarding_experiment_enabled_for_user? - Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_a, subject: current_user) || - Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_b, subject: current_user) - end - def onboarding_sections_data { workspace: { diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index 0384f82f1f1..e7d69c38a54 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -32,7 +32,7 @@ module Nav id: 'new_project', title: _('New project/repository'), href: new_project_path(namespace_id: group.id), - data: { track_event: 'click_link_new_project_group', track_label: 'plus_menu_dropdown' } + data: { track_action: 'click_link_new_project_group', track_label: 'plus_menu_dropdown' } ) ) end @@ -43,7 +43,7 @@ module Nav id: 'new_subgroup', title: _('New subgroup'), href: new_group_path(parent_id: group.id), - data: { track_event: 'click_link_new_subgroup', track_label: 'plus_menu_dropdown' } + data: { track_action: 'click_link_new_subgroup', track_label: 'plus_menu_dropdown' } ) ) end @@ -74,7 +74,7 @@ module Nav id: 'new_issue', title: _('New issue'), href: new_project_issue_path(project), - data: { track_event: 'click_link_new_issue', track_label: 'plus_menu_dropdown', qa_selector: 'new_issue_link' } + data: { track_action: 'click_link_new_issue', track_label: 'plus_menu_dropdown', qa_selector: 'new_issue_link' } ) ) end @@ -85,7 +85,7 @@ module Nav id: 'new_mr', title: _('New merge request'), href: project_new_merge_request_path(merge_project), - data: { track_event: 'click_link_new_mr', track_label: 'plus_menu_dropdown' } + data: { track_action: 'click_link_new_mr', track_label: 'plus_menu_dropdown' } ) ) end @@ -96,7 +96,7 @@ module Nav id: 'new_snippet', title: _('New snippet'), href: new_project_snippet_path(project), - data: { track_event: 'click_link_new_snippet_project', track_label: 'plus_menu_dropdown' } + data: { track_action: 'click_link_new_snippet_project', track_label: 'plus_menu_dropdown' } ) ) end @@ -124,7 +124,7 @@ module Nav id: 'general_new_project', title: _('New project/repository'), href: new_project_path, - data: { track_event: 'click_link_new_project', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_project_link' } + data: { track_action: 'click_link_new_project', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_project_link' } ) ) end @@ -135,7 +135,7 @@ module Nav id: 'general_new_group', title: _('New group'), href: new_group_path, - data: { track_event: 'click_link_new_group', track_label: 'plus_menu_dropdown' } + data: { track_action: 'click_link_new_group', track_label: 'plus_menu_dropdown' } ) ) end @@ -146,7 +146,7 @@ module Nav id: 'general_new_snippet', title: _('New snippet'), href: new_snippet_path, - data: { track_event: 'click_link_new_snippet_parent', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_snippet_link' } + data: { track_action: 'click_link_new_snippet_parent', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_snippet_link' } ) ) end @@ -164,7 +164,7 @@ module Nav emoji: ('shaking_hands' if experiment_enabled?(:invite_members_new_dropdown)), href: href, data: { - track_event: 'click_link', + track_action: 'click_link', track_label: tracking_label, track_property: experiment_tracking_category_and_group(:invite_members_new_dropdown) } diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index 052b8339ebd..3055ad57b80 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -98,7 +98,7 @@ module Nav builder.add_primary_menu_item_with_shortcut( active: nav == 'project' || active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]), css_class: 'qa-projects-dropdown', - data: { track_label: "projects_dropdown", track_event: "click_dropdown" }, + data: { track_label: "projects_dropdown", track_action: "click_dropdown" }, view: PROJECTS_VIEW, shortcut_href: dashboard_projects_path, **projects_menu_item_attrs @@ -112,7 +112,7 @@ module Nav builder.add_primary_menu_item_with_shortcut( active: nav == 'group' || active_nav_link?(path: %w[dashboard/groups explore/groups]), css_class: 'qa-groups-dropdown', - data: { track_label: "groups_dropdown", track_event: "click_dropdown" }, + data: { track_label: "groups_dropdown", track_action: "click_dropdown" }, view: GROUPS_VIEW, shortcut_href: dashboard_groups_path, **groups_menu_item_attrs diff --git a/app/helpers/notify_helper.rb b/app/helpers/notify_helper.rb index c0ba93f4a30..ed96f3cef4f 100644 --- a/app/helpers/notify_helper.rb +++ b/app/helpers/notify_helper.rb @@ -20,4 +20,21 @@ 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/packages_helper.rb b/app/helpers/packages_helper.rb index 1a466c9d170..ebf30fb3538 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -64,9 +64,8 @@ module PackagesHelper project.container_repositories.exists? end - def package_details_data(project, package, use_presenter = false) + def package_details_data(project, package) { - package: use_presenter ? package_from_presenter(package) : nil, package_id: package.id, can_delete: can?(current_user, :destroy_package, project).to_s, svg_path: image_path('illustrations/no-packages.svg'), diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index f6ed567c9ea..09fc1ab9d50 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -6,15 +6,12 @@ module ProfilesHelper verified_emails = user.verified_emails - [private_email] [ + [s_('Use primary email (%{email})') % { email: user.email }, ''], [s_("Profiles|Use a private email - %{email}").html_safe % { email: private_email }, Gitlab::PrivateCommitEmail::TOKEN], *verified_emails ] end - def selected_commit_email(user) - user.read_attribute(:commit_email) || user.commit_email - end - def attribute_provider_label(attribute) user_synced_attributes_metadata = current_user.user_synced_attributes_metadata if user_synced_attributes_metadata&.synced?(attribute) @@ -38,6 +35,21 @@ module ProfilesHelper status&.availability == availability_values[:busy] end + 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)) + else + classes << 'gl-display-inline-block' + classes << if breakpoint.nil? + 'middle-dot-divider' + else + "middle-dot-divider-#{breakpoint}" + end + end + end + end + # Overridden in EE::ProfilesHelper#ssh_key_expiration_tooltip def ssh_key_expiration_tooltip(key) return key.errors.full_messages.join(', ') if key.errors.full_messages.any? diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f30223f6f1e..d7f1cd505e9 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -435,7 +435,7 @@ module ProjectsHelper def git_user_email if current_user - current_user.commit_email + current_user.commit_email_or_default else "your@email.com" end diff --git a/app/helpers/recaptcha_helper.rb b/app/helpers/recaptcha_helper.rb index 4ebac1d5b7f..0df62f7b715 100644 --- a/app/helpers/recaptcha_helper.rb +++ b/app/helpers/recaptcha_helper.rb @@ -5,3 +5,5 @@ module RecaptchaHelper !!Gitlab::Recaptcha.enabled? end end + +RecaptchaHelper.prepend_mod diff --git a/app/helpers/routing/pseudonymization_helper.rb b/app/helpers/routing/pseudonymization_helper.rb new file mode 100644 index 00000000000..1d9320f0106 --- /dev/null +++ b/app/helpers/routing/pseudonymization_helper.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Routing + module PseudonymizationHelper + def masked_page_url + return unless Feature.enabled?(:mask_page_urls, type: :ops) + + mask_params(Rails.application.routes.recognize_path(request.original_fullpath)) + rescue ActionController::RoutingError, URI::InvalidURIError + nil + end + + private + + def mask_params(request_params) + return if request_params[:action] == 'new' + + namespace_type = request_params[:controller].split('/')[1] + + namespace_type.present? ? url_with_namespace_type(request_params, namespace_type) : url_without_namespace_type(request_params) + end + + def url_without_namespace_type(request_params) + masked_url = "#{request.protocol}#{request.host_with_port}" + + masked_url += case request_params[:controller] + when 'groups' + "/namespace:#{group.id}" + when 'projects' + "/namespace:#{project.namespace.id}/project:#{project.id}" + when 'root' + '' + else + "#{request.path}" + end + + masked_url += request.query_string.present? ? "?#{request.query_string}" : '' + + masked_url + end + + def url_with_namespace_type(request_params, namespace_type) + masked_url = "#{request.protocol}#{request.host_with_port}" + + if request_params.has_key?(:project_id) + masked_url += "/namespace:#{project.namespace.id}/project:#{project.id}/-/#{namespace_type}" + end + + if request_params.has_key?(:id) + masked_url += namespace_type == 'blob' ? '/:repository_path' : "/#{request_params[:id]}" + end + + masked_url += request.query_string.present? ? "?#{request.query_string}" : '' + + masked_url + end + end +end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 409a3e65fe3..b8e58e3afb1 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -443,6 +443,10 @@ module SearchHelper _("Open") end end + + def feature_flag_tab_enabled?(flag) + @group || Feature.enabled?(flag, current_user, type: :ops, default_enabled: true) + end end SearchHelper.prepend_mod_with('SearchHelper') diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 117f662fec6..e9466a9e97e 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -22,11 +22,21 @@ module SessionsHelper # creates a new session after login, so the short TTL doesn't even need to # be extended. def limit_session_time + set_session_time(Settings.gitlab['unauthenticated_session_expire_delay']) + end + + def ensure_authenticated_session_time + set_session_time(nil) + end + + def set_session_time(expiry_s) # Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259 return unless request.env['rack.session.options'] - # This works because Rack uses these options every time a request is handled: - # https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342 - request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay'] + # This works because Rack uses these options every time a request is handled, and redis-store + # uses the Rack setting first: + # 1. https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342 + # 2. https://github.com/redis-store/redis-store/blob/3acfa95f4eb6260c714fdb00a3d84be8eedc13b2/lib/redis/store/ttl.rb#L32 + request.env['rack.session.options'][:expire_after] = expiry_s end end diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 77af6e37099..9002fdda128 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -87,8 +87,7 @@ module SidebarsHelper { current_user: user, container: project, - learn_gitlab_experiment_enabled: learn_gitlab_experiment_enabled?(project), - learn_gitlab_experiment_tracking_category: learn_gitlab_experiment_tracking_category, + learn_gitlab_enabled: learn_gitlab_enabled?(project), current_ref: current_ref, jira_issues_integration: project_jira_issues_integration?, can_view_pipeline_editor: can_view_pipeline_editor?(project), diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 7fa85d143f7..b28e5ff39b2 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -37,7 +37,8 @@ module SortingHelper sort_value_contacted_date => sort_title_contacted_date, sort_value_relative_position => sort_title_relative_position, sort_value_size => sort_title_size, - sort_value_expire_date => sort_title_expire_date + sort_value_expire_date => sort_title_expire_date, + sort_value_title => sort_title_title } end # rubocop: enable Metrics/AbcSize @@ -188,7 +189,8 @@ module SortingHelper sort_value_due_date_later => sort_value_due_date, sort_value_merged_recently => sort_value_merged_date, sort_value_closed_recently => sort_value_closed_date, - sort_value_least_popular => sort_value_popularity + sort_value_least_popular => sort_value_popularity, + sort_value_title_desc => sort_value_title } end @@ -205,7 +207,8 @@ module SortingHelper sort_value_closed_date => sort_value_closed_recently, sort_value_closed_earlier => sort_value_closed_recently, sort_value_popularity => sort_value_least_popular, - sort_value_most_popular => sort_value_least_popular + sort_value_most_popular => sort_value_least_popular, + sort_value_title => sort_value_title_desc }.merge(issuable_sort_option_overrides) end diff --git a/app/helpers/sorting_titles_values_helper.rb b/app/helpers/sorting_titles_values_helper.rb index f4117d690f3..75ba6e8a153 100644 --- a/app/helpers/sorting_titles_values_helper.rb +++ b/app/helpers/sorting_titles_values_helper.rb @@ -138,6 +138,10 @@ module SortingTitlesValuesHelper s_('SortOptions|Start soon') end + def sort_title_title + s_('SortOptions|Title') + end + def sort_title_upvotes s_('SortOptions|Most popular') end @@ -307,6 +311,14 @@ module SortingTitlesValuesHelper 'start_date_asc' end + def sort_value_title + 'title_asc' + end + + def sort_value_title_desc + 'title_desc' + end + def sort_value_upvotes 'upvotes_desc' end diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 521423fbb94..1d8b657025c 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -39,7 +39,8 @@ module SystemNoteHelper 'alert_issue_added' => 'issues', 'new_alert_added' => 'warning', 'severity' => 'information-o', - 'cloned' => 'documents' + 'cloned' => 'documents', + 'issue_type' => 'pencil-square' }.freeze def system_note_icon_name(note) diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index f5a74a3f57d..2c3dc243d85 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -9,6 +9,7 @@ module UserCalloutsHelper 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' def show_gke_cluster_integration_callout?(project) active_nav_link?(controller: sidebar_operations_paths) && @@ -27,7 +28,7 @@ module UserCalloutsHelper def render_dashboard_ultimate_trial(user) end - def render_account_recovery_regular_check + def render_two_factor_auth_recovery_settings_check end def show_suggest_popover? @@ -53,7 +54,14 @@ module UserCalloutsHelper !user_dismissed?(REGISTRATION_ENABLED_CALLOUT) end - def dismiss_account_recovery_regular_check + 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 private @@ -63,6 +71,43 @@ module UserCalloutsHelper 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 + + set_dismissed_from_cookie(group) + + current_user.dismissed_callout_for_group?(feature_name: feature_name, + group: group, + ignore_dismissal_earlier_than: ignore_dismissal_earlier_than) + end + + def set_dismissed_from_cookie(group) + # bridge function for one milestone to try and not annoy users who might have already dismissed this alert + # remove in 14.4 or 14.5? https://gitlab.com/gitlab-org/gitlab/-/issues/340322 + dismissed_key = "invite_#{group.id}_#{current_user.id}" + + if cookies[dismissed_key].present? + params = { + feature_name: INVITE_MEMBERS_BANNER, + group_id: group.id + } + + Users::DismissGroupCalloutService.new( + container: nil, current_user: current_user, params: params + ).execute + + cookies.delete dismissed_key + end + 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 |