diff options
Diffstat (limited to 'app/helpers')
40 files changed, 369 insertions, 152 deletions
diff --git a/app/helpers/admin/deploy_key_helper.rb b/app/helpers/admin/deploy_key_helper.rb new file mode 100644 index 00000000000..caf3757a68e --- /dev/null +++ b/app/helpers/admin/deploy_key_helper.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Admin + module DeployKeyHelper + def admin_deploy_keys_data + { + edit_path: edit_admin_deploy_key_path(':id'), + delete_path: admin_deploy_key_path(':id'), + create_path: new_admin_deploy_key_path, + empty_state_svg_path: image_path('illustrations/empty-state/empty-deploy-keys-lg.svg') + } + end + end +end diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 60e37c96f61..5ca360f38da 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -39,14 +39,14 @@ module AppearancesHelper if current_appearance&.header_logo? image_tag current_appearance.header_logo_path, class: 'brand-header-logo' else - render 'shared/logo.svg' + render partial: 'shared/logo', formats: :svg end end # Skip the 'GitLab' type logo when custom brand logo is set def brand_header_logo_type unless current_appearance&.header_logo? - render 'shared/logo_type.svg' + render partial: 'shared/logo_type', formats: :svg end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 2103a37180f..b8ee71daeee 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -404,6 +404,10 @@ module ApplicationSettingsHelper :keep_latest_artifact, :whats_new_variant, :user_deactivation_emails_enabled, + :sentry_enabled, + :sentry_dsn, + :sentry_clientside_dsn, + :sentry_environment, :sidekiq_job_limiter_mode, :sidekiq_job_limiter_compression_threshold_bytes, :sidekiq_job_limiter_limit_bytes, diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index a0c3a6f2f52..6fe92a5a978 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -8,6 +8,7 @@ module AuthHelper azure_oauth2 bitbucket facebook + dingtalk github gitlab google_oauth2 diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index 33d5bae88f4..c26a73028b9 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -43,7 +43,7 @@ module BoardsHelper def build_issue_link_base if board.group_board? - "#{group_path(@board.group)}/:project_path/issues" + "/:project_path/-/issues" else project_issues_path(@project) end diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb index ade7c48b03f..3a622a65685 100644 --- a/app/helpers/breadcrumbs_helper.rb +++ b/app/helpers/breadcrumbs_helper.rb @@ -27,10 +27,10 @@ module BreadcrumbsHelper end end - def add_to_breadcrumb_dropdown(link, location: :before) - @breadcrumb_dropdown_links ||= {} - @breadcrumb_dropdown_links[location] ||= [] - @breadcrumb_dropdown_links[location] << link + def add_to_breadcrumb_collapsed_links(link, location: :before) + @breadcrumb_collapsed_links ||= {} + @breadcrumb_collapsed_links[location] ||= [] + @breadcrumb_collapsed_links[location] << link end def push_to_schema_breadcrumb(text, link) diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb index 6be46b40023..6104a1256d5 100644 --- a/app/helpers/ci/pipelines_helper.rb +++ b/app/helpers/ci/pipelines_helper.rb @@ -67,6 +67,17 @@ module Ci ] end + def has_pipeline_badges?(pipeline) + pipeline.child? || + pipeline.latest? || + pipeline.merge_train_pipeline? || + pipeline.has_yaml_errors? || + pipeline.failure_reason? || + pipeline.auto_devops_source? || + pipeline.detached_merge_request_pipeline? || + pipeline.stuck? + end + private def warning_markdown(pipeline) diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index ec10610714b..17057505173 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -6,27 +6,30 @@ module Ci def runner_status_icon(runner, size: 16, icon_class: '') status = runner.status + active = runner.active title = '' icon = 'warning-solid' span_class = '' case status + when :online + if active + title = s_("Runners|Runner is online, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } + icon = 'status-active' + span_class = 'gl-text-green-500' + else + title = s_("Runners|Runner is paused, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } + icon = 'status-paused' + span_class = 'gl-text-gray-600' + end when :not_connected title = s_("Runners|New runner, has not connected yet") icon = 'warning-solid' - when :online - title = s_("Runners|Runner is online, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } - icon = 'status-active' - span_class = 'gl-text-green-500' when :offline title = s_("Runners|Runner is offline, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } icon = 'status-failed' span_class = 'gl-text-red-500' - when :paused - title = s_("Runners|Runner is paused, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } - icon = 'status-paused' - span_class = 'gl-text-gray-600' end content_tag(:span, class: span_class, title: title, data: { toggle: 'tooltip', container: 'body', testid: 'runner_status_icon', qa_selector: "runner_status_#{status}_content" }) do @@ -57,11 +60,30 @@ module Ci end end + def admin_runners_data_attributes + { + # Runner install help page is external, located at + # https://gitlab.com/gitlab-org/gitlab-runner + runner_install_help_page: 'https://docs.gitlab.com/runner/install/', + registration_token: Gitlab::CurrentSettings.runners_registration_token, + + # All runner counts are returned as formatted strings + active_runners_count: Ci::Runner.online.count.to_s, + all_runners_count: limited_counter_with_delimiter(Ci::Runner), + instance_runners_count: limited_counter_with_delimiter(Ci::Runner.instance_type), + group_runners_count: limited_counter_with_delimiter(Ci::Runner.group_type), + project_runners_count: limited_counter_with_delimiter(Ci::Runner.project_type) + } + end + def group_shared_runners_settings_data(group) { update_path: api_v4_groups_path(id: group.id), shared_runners_availability: group.shared_runners_setting, - parent_shared_runners_availability: group.parent&.shared_runners_setting + parent_shared_runners_availability: group.parent&.shared_runners_setting, + runner_enabled: Namespace::SR_ENABLED, + runner_disabled: Namespace::SR_DISABLED_AND_UNOVERRIDABLE, + runner_allow_override: Namespace::SR_DISABLED_WITH_OVERRIDE } end diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index c355fa5cc67..93b6b4e8fe2 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -12,35 +12,36 @@ module ClustersHelper end end - def display_cluster_agents?(_clusterable) - false + def display_cluster_agents?(clusterable) + clusterable.is_a?(Project) end - def js_cluster_agents_list_data(clusterable_project) - { - default_branch_name: clusterable_project.default_branch, - empty_state_image: image_path('illustrations/clusters_empty.svg'), - project_path: clusterable_project.full_path, - agent_docs_url: help_page_path('user/clusters/agent/index'), - install_docs_url: help_page_path('administration/clusters/kas'), - get_started_docs_url: help_page_path('user/clusters/agent/index', anchor: 'define-a-configuration-repository'), - integration_docs_url: help_page_path('user/clusters/agent/index', anchor: 'get-started-with-gitops-and-the-gitlab-agent'), - kas_address: Gitlab::Kas.external_url - } - end - - def js_clusters_list_data(path = nil) + def js_clusters_list_data(clusterable) { ancestor_help_path: help_page_path('user/group/clusters/index', anchor: 'cluster-precedence'), - endpoint: path, + endpoint: clusterable.index_path(format: :json), img_tags: { aws: { path: image_path('illustrations/logos/amazon_eks.svg'), text: s_('ClusterIntegration|Amazon EKS') }, default: { path: image_path('illustrations/logos/kubernetes.svg'), text: _('Kubernetes Cluster') }, gcp: { path: image_path('illustrations/logos/google_gke.svg'), text: s_('ClusterIntegration|Google GKE') } - } + }, + clusters_empty_state_image: image_path('illustrations/empty-state/empty-state-clusters.svg'), + empty_state_help_text: clusterable.empty_state_help_text, + new_cluster_path: clusterable.new_path(tab: 'create'), + can_add_cluster: clusterable.can_add_cluster?.to_s } end + def js_clusters_data(clusterable) + { + default_branch_name: clusterable.default_branch, + empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'), + project_path: clusterable.full_path, + add_cluster_path: clusterable.new_path(tab: 'add'), + kas_address: Gitlab::Kas.external_url + }.merge(js_clusters_list_data(clusterable)) + end + def js_cluster_form_data(cluster, can_edit) { enabled: cluster.enabled?.to_s, diff --git a/app/helpers/emoji_helper.rb b/app/helpers/emoji_helper.rb index 51b7fd7f352..c390924f7e3 100644 --- a/app/helpers/emoji_helper.rb +++ b/app/helpers/emoji_helper.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true module EmojiHelper - def emoji_icon(*args) - raw Gitlab::Emoji.gl_emoji_tag(*args) + def emoji_icon(emoji_name, *options) + emoji = TanukiEmoji.find_by_alpha_code(emoji_name) + raw Gitlab::Emoji.gl_emoji_tag(emoji, *options) end end diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index b2842664879..cde45e7bc0f 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -69,9 +69,7 @@ module EnvironmentsHelper '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)}", - 'prometheus_alerts_available' => "#{can?(current_user, :read_prometheus_alerts, project)}", - 'dashboard_timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase, - 'has_managed_prometheus' => has_managed_prometheus?(project).to_s + 'dashboard_timezone' => project.metrics_setting_dashboard_timezone.to_s.upcase } end @@ -86,10 +84,6 @@ module EnvironmentsHelper } end - def has_managed_prometheus?(project) - project.prometheus_integration&.prometheus_available? == true - end - def metrics_dashboard_base_path(environment, project) # This is needed to support our transition from environment scoped metric paths to project scoped. if project diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index 3a94f7d47c2..45ca820f7b3 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -26,6 +26,10 @@ module GraphHelper def should_render_dora_charts false end + + def should_render_quality_summary + false + end end GraphHelper.prepend_mod_with('GraphHelper') diff --git a/app/helpers/groups/settings_helper.rb b/app/helpers/groups/settings_helper.rb new file mode 100644 index 00000000000..1b391680996 --- /dev/null +++ b/app/helpers/groups/settings_helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Groups + module SettingsHelper + include GroupsHelper + + def group_settings_confirm_modal_data(group, remove_form_id = nil) + { + remove_form_id: remove_form_id, + button_text: _('Remove group'), + button_testid: 'remove-group-button', + disabled: group.paid?.to_s, + confirm_danger_message: remove_group_message(group), + phrase: group.full_path + } + end + end +end + +Groups::SettingsHelper.prepend_mod_with('Groups::SettingsHelper') diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 30aaa0a5acc..9ba7d004d6c 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -39,7 +39,7 @@ module GroupsHelper sorted_ancestors(group).with_route.reverse_each.with_index do |parent, index| if index > 0 - add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true, for_dropdown: true), location: :before) + add_to_breadcrumb_collapsed_links(group_title_link(parent), location: :before) else full_title << breadcrumb_list_item(group_title_link(parent, hidable: false)) end @@ -47,7 +47,7 @@ module GroupsHelper push_to_schema_breadcrumb(simple_sanitize(parent.name), group_path(parent)) end - full_title << render("layouts/nav/breadcrumbs/collapsed_dropdown", location: :before, title: _("Show parent subgroups")) + full_title << render("layouts/nav/breadcrumbs/collapsed_inline_list", location: :before, title: _("Show all breadcrumbs")) full_title << breadcrumb_list_item(group_title_link(group)) push_to_schema_breadcrumb(simple_sanitize(group.name), group_path(group)) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index c38b4a7aedf..32d808c960c 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -9,9 +9,7 @@ module IconsHelper def custom_icon(icon_name, size: DEFAULT_ICON_SIZE) memoized_icon("#{icon_name}_#{size}") do - # We can't simply do the below, because there are some .erb SVGs. - # File.read(Rails.root.join("app/views/shared/icons/_#{icon_name}.svg")).html_safe - render "shared/icons/#{icon_name}.svg", size: size + render partial: "shared/icons/#{icon_name}", formats: :svg, locals: { size: size } end end diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index 8819aa9e9cc..bb4a7fef6be 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -132,6 +132,20 @@ module IntegrationsHelper end end + def zentao_issue_breadcrumb_link(issue) + link_to issue[:web_url], { target: '_blank', rel: 'noopener noreferrer', class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do + icon = image_tag image_path('logos/zentao.svg'), width: 15, height: 15, class: 'gl-mr-2' + [icon, html_escape(issue[:id])].join.html_safe + end + end + + def zentao_issues_show_data + { + issues_show_path: project_integrations_zentao_issue_path(@project, params[:id], format: :json), + issues_list_path: project_integrations_zentao_issues_path(@project) + } + end + extend self private diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index d9bd64f4c2e..01ae0ce4f31 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -42,6 +42,14 @@ module InviteMembersHelper 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? + 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 @@ -71,4 +79,19 @@ module InviteMembersHelper def users_filter_data(group) {} end + + def show_invite_members_for_task? + return unless current_user && experiment(:invite_members_for_task).enabled? + + 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 diff --git a/app/helpers/issuables_description_templates_helper.rb b/app/helpers/issuables_description_templates_helper.rb index a5b9a6eee80..6b546d5c6fc 100644 --- a/app/helpers/issuables_description_templates_helper.rb +++ b/app/helpers/issuables_description_templates_helper.rb @@ -32,14 +32,17 @@ module IssuablesDescriptionTemplatesHelper @template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names(project, issuable_type.pluralize) end - # Overriden on EE::IssuablesDescriptionTemplatesHelper to include inherited templates names - def issuable_templates_names(issuable, include_inherited_templates = false) + def selected_template(issuable) all_templates = issuable_templates(ref_project, issuable.to_ability_name) - all_templates.values.flatten.map { |tpl| tpl[:name] if tpl[:project_id] == ref_project.id }.compact.uniq + + # Only local templates will be listed if licenses for inherited templates are not present + all_templates = all_templates.values.flatten.map { |tpl| tpl[:name] }.compact.uniq + + all_templates.find { |tmpl_name| tmpl_name == params[:issuable_template] } end - def selected_template(issuable) - params[:issuable_template] if issuable_templates_names(issuable, true).any? { |tmpl_name| tmpl_name == params[:issuable_template] } + def available_service_desk_templates_for(project) + issuable_templates(project, 'issue').flatten.to_json end def template_names_path(parent, issuable) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 24c6ef8cd68..07f5adae272 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -257,7 +257,8 @@ module IssuablesHelper 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) + isHidden: issue_hidden?(issuable), + canCreateIncident: create_issue_type_allowed?(issuable.project, :incident) } end @@ -284,9 +285,7 @@ module IssuablesHelper end def issuables_count_for_state(issuable_type, 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] + Gitlab::IssuablesCountForState.new(finder, store_in_redis_cache: true)[state] end def close_issuable_path(issuable) @@ -442,7 +441,7 @@ module IssuablesHelper end def format_count(issuable_type, count, threshold) - if issuable_type == :issues && parent.is_a?(Group) && parent.cached_issues_state_count_enabled? + if issuable_type == :issues && parent.is_a?(Group) format_cached_count(threshold, count) else number_with_delimiter(count) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 49f7d9aeef1..a88ca6f6b11 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module IssuesHelper + include Issues::IssueTypeHelpers + def issue_css_classes(issue) classes = ["issue"] classes << "closed" if issue.closed? @@ -190,6 +192,7 @@ module IssuesHelper { can_create_issue: show_new_issue_link?(project).to_s, + can_create_incident: create_issue_type_allowed?(project, :incident).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, @@ -233,6 +236,7 @@ module IssuesHelper 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), quick_actions_help_path: help_page_path('user/project/quick_actions'), + releases_path: project_releases_path(project, format: :json), reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'), show_new_issue_link: show_new_issue_link?(project).to_s ) diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index 4fb7a05a0e9..08a30c4d53b 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -7,10 +7,32 @@ module LearnGitlabHelper learn_gitlab_onboarding_available?(project) end + def learn_gitlab_data(project) + { + actions: onboarding_actions_data(project).to_json, + sections: onboarding_sections_data.to_json + } + end + + def learn_gitlab_onboarding_available?(project) + OnboardingProgress.onboarding?(project.namespace) && + LearnGitlab::Project.new(current_user).available? + end + + private + def onboarding_actions_data(project) attributes = onboarding_progress(project).attributes.symbolize_keys - action_urls.to_h do |action, url| + urls_to_use = nil + + experiment(:change_continuous_onboarding_link_urls) do |e| + e.namespace = project.namespace + e.use { urls_to_use = action_urls } + e.try { urls_to_use = new_action_urls(project) } + end + + urls_to_use.to_h do |action, url| [ action, url: url, @@ -34,18 +56,22 @@ module LearnGitlabHelper } end - def learn_gitlab_onboarding_available?(project) - OnboardingProgress.onboarding?(project.namespace) && - LearnGitlab::Project.new(current_user).available? - end - - private - def action_urls LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) } .merge(LearnGitlab::Onboarding::ACTION_DOC_URLS) end + def new_action_urls(project) + action_urls.merge( + issue_created: project_issues_path(project), + git_write: project_path(project), + pipeline_created: project_pipelines_path(project), + merge_request_created: project_merge_requests_path(project), + user_added: project_members_url(project), + security_scan_enabled: project_security_configuration_path(project) + ) + end + def learn_gitlab_project @learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project end @@ -54,3 +80,5 @@ module LearnGitlabHelper OnboardingProgress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord end end + +LearnGitlabHelper.prepend_mod_with('LearnGitlabHelper') diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index d3db5d24207..aac49cfa234 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -56,6 +56,14 @@ 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/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index 3055ad57b80..ecef2d38e54 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -267,6 +267,7 @@ module Nav builder.add_primary_menu_item(id: 'your', title: _('Your projects'), href: dashboard_projects_path) builder.add_primary_menu_item(id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path) builder.add_primary_menu_item(id: 'explore', title: _('Explore projects'), href: explore_root_path) + builder.add_primary_menu_item(id: 'topics', title: _('Explore topics'), href: topics_explore_projects_path) builder.add_secondary_menu_item(id: 'create', title: _('Create new project'), href: new_project_path) builder.build end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index fff7e5d1c7f..2dadaa0be0a 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -167,11 +167,11 @@ module NotesHelper } end - def discussions_path(issuable) + def discussions_path(issuable, **params) if issuable.is_a?(Issue) - discussions_project_issue_path(@project, issuable, format: :json) + discussions_project_issue_path(@project, issuable, params.merge(format: :json)) else - discussions_project_merge_request_path(@project, issuable, format: :json) + discussions_project_merge_request_path(@project, issuable, params.merge(format: :json)) end end @@ -188,7 +188,8 @@ module NotesHelper reopenPath: reopen_issuable_path(issuable), notesPath: notes_url, prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES), - lastFetchedAt: initial_last_fetched_at + lastFetchedAt: initial_last_fetched_at, + notesFilter: current_user&.notes_filter_for(issuable) } if issuable.is_a?(MergeRequest) diff --git a/app/helpers/one_trust_helper.rb b/app/helpers/one_trust_helper.rb index 9f92a73a4d4..55364d36a0e 100644 --- a/app/helpers/one_trust_helper.rb +++ b/app/helpers/one_trust_helper.rb @@ -4,7 +4,6 @@ module OneTrustHelper def one_trust_enabled? Feature.enabled?(:ecomm_instrumentation, type: :ops) && Gitlab.config.extra.has_key?('one_trust_id') && - Gitlab.config.extra.one_trust_id.present? && - !current_user + Gitlab.config.extra.one_trust_id.present? end end diff --git a/app/helpers/projects/alert_management_helper.rb b/app/helpers/projects/alert_management_helper.rb index b50e287a509..e03f2ae78bf 100644 --- a/app/helpers/projects/alert_management_helper.rb +++ b/app/helpers/projects/alert_management_helper.rb @@ -10,7 +10,6 @@ module Projects::AlertManagementHelper 'empty-alert-svg-path' => image_path('illustrations/alert-management-empty-state.svg'), 'user-can-enable-alert-management' => can?(current_user, :admin_operations, project).to_s, 'alert-management-enabled' => alert_management_enabled?(project).to_s, - 'has-managed-prometheus' => has_managed_prometheus?(project).to_s, 'text-query': params[:search], 'assignee-username-query': params[:assignee_username] } @@ -28,10 +27,6 @@ module Projects::AlertManagementHelper private - def has_managed_prometheus?(project) - project.prometheus_integration&.prometheus_available? == true - end - def alert_management_enabled?(project) !!( project.alert_management_alerts.any? || diff --git a/app/helpers/projects/incidents_helper.rb b/app/helpers/projects/incidents_helper.rb index dde2980817f..4b3ff8a3e83 100644 --- a/app/helpers/projects/incidents_helper.rb +++ b/app/helpers/projects/incidents_helper.rb @@ -11,7 +11,8 @@ module Projects::IncidentsHelper 'empty-list-svg-path' => image_path('illustrations/incident-empty-state.svg'), 'text-query': params[:search], 'author-username-query': params[:author_username], - 'assignee-username-query': params[:assignee_username] + 'assignee-username-query': params[:assignee_username], + 'can-create-incident': create_issue_type_allowed?(project, :incident).to_s } end end diff --git a/app/helpers/projects/security/configuration_helper.rb b/app/helpers/projects/security/configuration_helper.rb index dee106ab3ae..8281b1f8522 100644 --- a/app/helpers/projects/security/configuration_helper.rb +++ b/app/helpers/projects/security/configuration_helper.rb @@ -4,7 +4,7 @@ module Projects module Security module ConfigurationHelper def security_upgrade_path - 'https://about.gitlab.com/pricing/' + "https://#{ApplicationHelper.promo_host}/pricing/" end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e3b63d122d2..8366b25d2bc 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -196,12 +196,26 @@ module ProjectsHelper cookies["hide_auto_devops_implicitly_enabled_banner_#{project.id}".to_sym].blank? end - def link_to_set_password - if current_user.require_password_creation_for_git? - link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path - else - link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path - end + def no_password_message + push_pull_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('gitlab-basics/start-using-git', anchor: 'pull-and-push') } + clone_with_https_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('gitlab-basics/start-using-git', anchor: 'clone-with-https') } + set_password_link_start = '<a href="%{url}">'.html_safe % { url: edit_profile_password_path } + set_up_pat_link_start = '<a href="%{url}">'.html_safe % { url: profile_personal_access_tokens_path } + + message = if current_user.require_password_creation_for_git? + _('Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_password_link_start}set a password%{link_end} or %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}.') + else + _('Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}.') + end + + html_escape(message) % { + push_pull_link_start: push_pull_link_start, + protocol: gitlab_config.protocol.upcase, + clone_with_https_link_start: clone_with_https_link_start, + set_password_link_start: set_password_link_start, + set_up_pat_link_start: set_up_pat_link_start, + link_end: '</a>'.html_safe + } end # Returns true if any projects are present. @@ -382,6 +396,15 @@ module ProjectsHelper "" end + # Returns the confirm phrase the user needs to type in order to delete the project + # + # Thus the phrase should include the namespace to make it very clear to the + # user which project is subject to deletion. + # Relevant issue: https://gitlab.com/gitlab-org/gitlab/-/issues/343591 + def delete_confirm_phrase(project) + project.path_with_namespace + end + private def tab_ability_map @@ -590,6 +613,7 @@ module ProjectsHelper %w[ environments clusters + cluster_agents functions error_tracking alert_management diff --git a/app/helpers/recaptcha_helper.rb b/app/helpers/recaptcha_helper.rb index 0df62f7b715..5b17ab4b815 100644 --- a/app/helpers/recaptcha_helper.rb +++ b/app/helpers/recaptcha_helper.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true module RecaptchaHelper - def show_recaptcha_sign_up? + def recaptcha_enabled? !!Gitlab::Recaptcha.enabled? end + alias_method :show_recaptcha_sign_up?, :recaptcha_enabled? end RecaptchaHelper.prepend_mod diff --git a/app/helpers/reminder_emails_helper.rb b/app/helpers/reminder_emails_helper.rb index bffb3cf7751..132fc3b784c 100644 --- a/app/helpers/reminder_emails_helper.rb +++ b/app/helpers/reminder_emails_helper.rb @@ -7,7 +7,8 @@ module ReminderEmailsHelper s_('InviteReminderEmail|Invitation pending') when 1 if format == :html - s_('InviteReminderEmail|Hey there %{wave_emoji}').html_safe % { wave_emoji: Gitlab::Emoji.gl_emoji_tag('wave') } + wave_emoji_tag = Gitlab::Emoji.gl_emoji_tag(TanukiEmoji.find_by_alpha_code('wave')) + s_('InviteReminderEmail|Hey there %{wave_emoji}').html_safe % { wave_emoji: wave_emoji_tag } else s_('InviteReminderEmail|Hey there!') end diff --git a/app/helpers/routing/pseudonymization_helper.rb b/app/helpers/routing/pseudonymization_helper.rb index b73e49803ae..ac30669dc83 100644 --- a/app/helpers/routing/pseudonymization_helper.rb +++ b/app/helpers/routing/pseudonymization_helper.rb @@ -2,58 +2,86 @@ module Routing module PseudonymizationHelper - def masked_page_url - return unless Feature.enabled?(:mask_page_urls, type: :ops) + class MaskHelper + QUERY_PARAMS_TO_NOT_MASK = %w[].freeze - mask_params(Rails.application.routes.recognize_path(request.original_fullpath)) - rescue ActionController::RoutingError, URI::InvalidURIError => e - Gitlab::ErrorTracking.track_exception(e, url: request.original_fullpath) - nil - end + def initialize(request_object, group, project) + @request = request_object + @group = group + @project = project + end + + def mask_params + return default_root_url + @request.original_fullpath unless has_maskable_params? - private + masked_params = @request.path_parameters.to_h do |key, value| + case key + when :project_id + [key, "project#{@project&.id}"] + when :namespace_id, :group_id + namespace = @group || @project&.namespace + [key, "namespace#{namespace&.id}"] + when :id + [key, mask_id(value)] + else + [key, value] + end + end - def mask_params(request_params) - return if request_params[:action] == 'new' + Gitlab::Routing.url_helpers.url_for(masked_params.merge(params: masked_query_params)) + end - namespace_type = request_params[:controller].split('/')[1] + private - namespace_type.present? ? url_with_namespace_type(request_params, namespace_type) : url_without_namespace_type(request_params) - end + def mask_id(value) + if @request.path_parameters[:controller] == 'projects/blob' + ':repository_path' + elsif @request.path_parameters[:controller] == 'projects' + "project#{@project&.id}" + elsif @request.path_parameters[:controller] == 'groups' + "namespace#{@group&.id}" + else + value + end + end - def url_without_namespace_type(request_params) - masked_url = "#{request.protocol}#{request.host_with_port}" + def has_maskable_params? + request_params = @request.path_parameters.to_h + request_params.key?(:namespace_id) || request_params.key?(:group_id) || request_params.key?(:project_id) || request_params.key?(:id) || @request.query_string.present? + end - 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 + def masked_query_params + return {} unless @request.query_string.present? - masked_url += request.query_string.present? ? "?#{request.query_string}" : '' + query_string_hash = Rack::Utils.parse_nested_query(@request.query_string) - masked_url - end + query_string_hash.keys.each do |key| + next if QUERY_PARAMS_TO_NOT_MASK.include?(key) - def url_with_namespace_type(request_params, namespace_type) - masked_url = "#{request.protocol}#{request.host_with_port}" + query_string_hash[key] = "masked_#{key}" + end - if request_params.has_key?(:project_id) - masked_url += "/namespace:#{project.namespace_id}/project:#{project.id}/-/#{namespace_type}" + query_string_hash end - if request_params.has_key?(:id) - masked_url += namespace_type == 'blob' ? '/:repository_path' : "/#{request_params[:id]}" + def default_root_url + Gitlab::Routing.url_helpers.root_url(only_path: false) end + end - masked_url += request.query_string.present? ? "?#{request.query_string}" : '' + def masked_page_url + 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.mask_params - masked_url + # We rescue all exception for time being till we test this helper extensively. + # Check https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72864#note_711515501 + rescue => e # rubocop:disable Style/RescueStandardError + Gitlab::ErrorTracking.track_exception(e, url: request.original_fullpath) + nil end end end diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb index d6a4d6ac57a..a60143db739 100644 --- a/app/helpers/storage_helper.rb +++ b/app/helpers/storage_helper.rb @@ -14,12 +14,13 @@ module StorageHelper counter_repositories: storage_counter(statistics.repository_size), counter_wikis: storage_counter(statistics.wiki_size), counter_build_artifacts: storage_counter(statistics.build_artifacts_size), + counter_pipeline_artifacts: storage_counter(statistics.pipeline_artifacts_size), counter_lfs_objects: storage_counter(statistics.lfs_objects_size), counter_snippets: storage_counter(statistics.snippets_size), counter_packages: storage_counter(statistics.packages_size), counter_uploads: storage_counter(statistics.uploads_size) } - _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters + _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters end end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index a6bb2f3b246..e53e35baac3 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -33,7 +33,6 @@ module TabHelper # :item_active - Overrides the default state focing the "active" css classes (optional). # def gl_tab_link_to(name = nil, options = {}, html_options = {}, &block) - tab_class = 'nav-item' link_classes = %w[nav-link gl-tab-nav-item] active_link_classes = %w[active gl-tab-nav-item-active gl-tab-nav-item-active-indigo] @@ -52,6 +51,8 @@ module TabHelper end html_options = html_options.except(:item_active) + 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 if block_given? @@ -210,3 +211,12 @@ 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/terms_helper.rb b/app/helpers/terms_helper.rb new file mode 100644 index 00000000000..5f321551413 --- /dev/null +++ b/app/helpers/terms_helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module TermsHelper + def terms_data(terms, redirect) + redirect_params = { redirect: redirect } if redirect + + { + terms: markdown_field(terms, :terms), + permissions: { + can_accept: can?(current_user, :accept_terms, terms), + can_decline: can?(current_user, :decline_terms, terms) + }, + paths: { + accept: accept_term_path(terms, redirect_params), + decline: decline_term_path(terms, redirect_params), + root: root_path + } + }.to_json + end +end diff --git a/app/helpers/time_zone_helper.rb b/app/helpers/time_zone_helper.rb index a0d9c8403e8..db355f5ff65 100644 --- a/app/helpers/time_zone_helper.rb +++ b/app/helpers/time_zone_helper.rb @@ -32,10 +32,16 @@ module TimeZoneHelper end end + def local_time_instance(timezone) + return Time.zone if timezone.blank? + + ActiveSupport::TimeZone.new(timezone) || Time.zone + end + def local_time(timezone) return if timezone.blank? - time_zone_instance = ActiveSupport::TimeZone.new(timezone) || Time.zone + time_zone_instance = local_time_instance(timezone) time_zone_instance.now.strftime("%-l:%M %p") end end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index e9dc271dbdd..79767ca76b7 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -24,6 +24,7 @@ module TodosHelper when Todo::UNMERGEABLE then 'Could not merge' when Todo::DIRECTLY_ADDRESSED then "directly addressed #{todo_action_subject(todo)} on" when Todo::MERGE_TRAIN_REMOVED then "Removed from Merge Train:" + when Todo::ATTENTION_REQUESTED then 'requested your attention on' end end diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index 1c67ca983fa..d8e69145c40 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -81,32 +81,11 @@ module UserCalloutsHelper 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 diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index 1b0d1254dc8..ba876f6cb65 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -9,7 +9,7 @@ module WikiHelper if page.persisted? titles << page.human_title breadcrumb_title(page.human_title) - wiki_breadcrumb_dropdown_links(page.slug) + wiki_breadcrumb_collapsed_links(page.slug) end titles << action if action @@ -39,14 +39,14 @@ module WikiHelper .join(' / ') end - def wiki_breadcrumb_dropdown_links(page_slug) + def wiki_breadcrumb_collapsed_links(page_slug) page_slug_split = page_slug.split('/') page_slug_split.pop(1) current_slug = "" page_slug_split .map do |dir_or_page| current_slug = "#{current_slug}#{dir_or_page}/" - add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, wiki_page_path(@wiki, current_slug)), location: :after + add_to_breadcrumb_collapsed_links link_to(WikiPage.unhyphenize(dir_or_page).capitalize, wiki_page_path(@wiki, current_slug)), location: :after end end diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index 4862282bc73..2460c956bb6 100644 --- a/app/helpers/workhorse_helper.rb +++ b/app/helpers/workhorse_helper.rb @@ -41,8 +41,8 @@ module WorkhorseHelper head :ok end - def send_dependency(token, url, filename) - headers.store(*Gitlab::Workhorse.send_dependency(token, url)) + def send_dependency(dependency_headers, url, filename) + headers.store(*Gitlab::Workhorse.send_dependency(dependency_headers, url)) headers['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: filename) headers['Content-Type'] = 'application/gzip' |