diff options
Diffstat (limited to 'app/helpers')
39 files changed, 359 insertions, 321 deletions
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 321a6e9395e..ddc682bc08a 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -7,6 +7,7 @@ module ApplicationSettingsHelper :gravatar_enabled?, :password_authentication_enabled_for_web?, :akismet_enabled?, + :spam_check_endpoint_enabled?, to: :'Gitlab::CurrentSettings.current_application_settings' def user_oauth_applications? @@ -60,6 +61,10 @@ module ApplicationSettingsHelper all_protocols_enabled? || Gitlab::CurrentSettings.enabled_git_access_protocol == 'http' end + def anti_spam_service_enabled? + akismet_enabled? || spam_check_endpoint_enabled? + end + def enabled_protocol_button(container, protocol) case protocol when 'ssh' @@ -278,6 +283,7 @@ module ApplicationSettingsHelper :max_export_size, :max_import_size, :max_pages_size, + :max_pages_custom_domains_per_project, :max_yaml_size_bytes, :max_yaml_depth, :metrics_method_call_threshold, @@ -434,12 +440,24 @@ module ApplicationSettingsHelper :runner_token_expiration_interval, :group_runner_token_expiration_interval, :project_runner_token_expiration_interval, - :pipeline_limit_per_project_user_sha + :pipeline_limit_per_project_user_sha, + :invitation_flow_enforcement ].tap do |settings| - settings << :deactivate_dormant_users unless Gitlab.com? + next if Gitlab.com? + + settings << :deactivate_dormant_users + settings << :deactivate_dormant_users_period end end + def runner_token_expiration_interval_attributes + { + instance_runner_token_expiration_interval: @application_setting.runner_token_expiration_interval, + group_runner_token_expiration_interval: @application_setting.group_runner_token_expiration_interval, + project_runner_token_expiration_interval: @application_setting.project_runner_token_expiration_interval + } + end + def external_authorization_service_attributes [ :external_auth_client_cert, diff --git a/app/helpers/badges_helper.rb b/app/helpers/badges_helper.rb index d48eae26a90..069c15433a5 100644 --- a/app/helpers/badges_helper.rb +++ b/app/helpers/badges_helper.rb @@ -1,25 +1,6 @@ # 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: @@ -53,47 +34,16 @@ module BadgesHelper # # See also https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-badge--default. def gl_badge_tag(*args, &block) + # Merge the options and html_options hashes if both are present, + # because the badge component wants a flat list of keyword args. + args.compact! + hashes, params = args.partition { |a| a.is_a? Hash } + options_hash = hashes.reduce({}, :merge) + if block - build_gl_badge_tag(capture(&block), *args) + render Pajamas::BadgeComponent.new(**options_hash), &block else - build_gl_badge_tag(*args) + render Pajamas::BadgeComponent.new(*params, **options_hash) 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)] - icon_classes = GL_ICON_CLASSES.dup << options.fetch(:icon_classes, nil) - - 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-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/blob_helper.rb b/app/helpers/blob_helper.rb index 2c84da4862a..6c09e15f56f 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -92,32 +92,6 @@ module BlobHelper end end - def replace_blob_link(project = @project, ref = @ref, path = @path, blob:) - modify_file_button( - project, - ref, - path, - blob: blob, - label: _("Replace"), - action: "replace", - btn_class: "default", - modal_type: "upload" - ) - end - - def delete_blob_link(project = @project, ref = @ref, path = @path, blob:) - modify_file_button( - project, - ref, - path, - blob: blob, - label: _("Delete"), - action: "delete", - btn_class: "default", - modal_type: "remove" - ) - end - def can_modify_blob?(blob, project = @project, ref = @ref) !blob.stored_externally? && can_edit_tree?(project, ref) end diff --git a/app/helpers/ci/builds_helper.rb b/app/helpers/ci/builds_helper.rb index b4a2cf7bb1e..afd0af18ba7 100644 --- a/app/helpers/ci/builds_helper.rb +++ b/app/helpers/ci/builds_helper.rb @@ -25,7 +25,7 @@ module Ci { page_path: project_job_path(@project, @build), build_status: @build.status, - build_stage: @build.stage, + build_stage: @build.stage_name, log_state: '' } end diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb index 6d63151769f..7b8290ac9ef 100644 --- a/app/helpers/ci/jobs_helper.rb +++ b/app/helpers/ci/jobs_helper.rb @@ -11,7 +11,7 @@ module Ci "runner_settings_url" => project_runners_path(@build.project, anchor: 'js-runners-settings'), "page_path" => project_job_path(@project, @build), "build_status" => @build.status, - "build_stage" => @build.stage, + "build_stage" => @build.stage_name, "log_state" => '', "build_options" => javascript_build_options, "retry_outdated_job_docs_url" => help_page_path('ci/pipelines/settings', anchor: 'retry-outdated-jobs') diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index 852eaeca5e3..0de84c0d61f 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -84,7 +84,6 @@ module Ci 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/', diff --git a/app/helpers/deploy_tokens_helper.rb b/app/helpers/deploy_tokens_helper.rb index 560d2fcd29f..597823cdac7 100644 --- a/app/helpers/deploy_tokens_helper.rb +++ b/app/helpers/deploy_tokens_helper.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module DeployTokensHelper - def expand_deploy_tokens_section?(deploy_token) - deploy_token.persisted? || - deploy_token.errors.present? || + def expand_deploy_tokens_section?(new_deploy_token, created_deploy_token) + created_deploy_token || + new_deploy_token.errors.present? || Rails.env.test? end @@ -14,7 +14,7 @@ module DeployTokensHelper def packages_registry_enabled?(group_or_project) Gitlab.config.packages.enabled && - can?(current_user, :read_package, group_or_project) + can?(current_user, :read_package, group_or_project&.packages_policy_subject) end def deploy_token_revoke_button_data(token:, group_or_project:) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 457502347ee..5c3b9d4b5ab 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -140,12 +140,12 @@ module DiffHelper if compare_url link_text = [ - _('Compare'), - ' ', - content_tag(:span, Commit.truncate_sha(diff_file.old_blob.id), class: 'commit-sha'), - '...', - content_tag(:span, Commit.truncate_sha(diff_file.blob.id), class: 'commit-sha') - ].join('').html_safe + _('Compare'), + ' ', + content_tag(:span, Commit.truncate_sha(diff_file.old_blob.id), class: 'commit-sha'), + '...', + content_tag(:span, Commit.truncate_sha(diff_file.blob.id), class: 'commit-sha') + ].join('').html_safe tooltip = _('Compare submodule commit revisions') link = content_tag(:span, link_to(link_text, compare_url, class: 'btn gl-button has-tooltip', title: tooltip), class: 'submodule-compare') diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index a910d3d7c9d..62e66b9a3ea 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module DropdownsHelper + # rubocop:disable Metrics/CyclomaticComplexity def dropdown_tag(toggle_text, options: {}, &block) content_tag :div, class: "dropdown #{options[:wrapper_class] if options.key?(:wrapper_class)}" do data_attr = { toggle: "dropdown" } @@ -16,7 +17,8 @@ module DropdownsHelper end content_tag_options = { class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}" } - content_tag_options[:data] = { qa_selector: "#{options[:dropdown_qa_selector]}" } if options[:dropdown_qa_selector] + content_tag_options[:data] = options[:dropdown_qa_selector] ? { qa_selector: "#{options[:dropdown_qa_selector]}" } : {} + content_tag_options[:data][:testid] = "#{options[:dropdown_testid]}" if options[:dropdown_testid] dropdown_output << content_tag(:div, content_tag_options) do output = [] @@ -46,6 +48,7 @@ module DropdownsHelper dropdown_output.html_safe end end + # rubocop:enable Metrics/CyclomaticComplexity def dropdown_toggle(toggle_text, data_attr, options = {}) default_label = data_attr[:default_label] diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb index f74eeeb8c6a..f2e24f54391 100644 --- a/app/helpers/form_helper.rb +++ b/app/helpers/form_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module FormHelper - def form_errors(model, type: 'form', truncate: [], pajamas_alert: true) + def form_errors(model, type: 'form', truncate: []) errors = model.errors return unless errors.any? @@ -64,7 +64,7 @@ module FormHelper field_name: "#{issuable_type}[assignee_ids][]", default_label: _('Unassigned'), 'max-select': 1, - 'dropdown-header': _('Assignee'), + 'dropdown-header': s_('SearchToken|Assignee'), multi_select: true, 'input-meta': 'name', 'always-show-selectbox': true, diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index bb92792de2d..f77bd6621f9 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -172,6 +172,15 @@ module GroupsHelper } end + def group_overview_tabs_app_data(group) + { + subgroups_and_projects_endpoint: group_children_path(group, format: :json), + shared_projects_endpoint: group_shared_projects_path(group, format: :json), + archived_projects_endpoint: group_children_path(group, format: :json, archived: 'only'), + current_group_visibility: group.visibility + }.merge(subgroups_and_projects_list_app_data(group)) + end + def enabled_git_access_protocol_options_for_group case ::Gitlab::CurrentSettings.enabled_git_access_protocol when nil, "" diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 4b463b9971d..ec1327cf7ae 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -24,7 +24,8 @@ module IdeHelper 'web-terminal-svg-path' => image_path('illustrations/web-ide_promotion.svg'), 'web-terminal-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'interactive-web-terminals-for-the-web-ide'), 'web-terminal-config-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'web-ide-configuration-file'), - 'web-terminal-runners-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'runner-configuration') + 'web-terminal-runners-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'runner-configuration'), + 'csp-nonce' => content_security_policy_nonce } end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8fd004233e2..96daf398243 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -156,7 +156,7 @@ module IssuablesHelper output = [] if issuable.respond_to?(:work_item_type) && WorkItems::Type::WI_TYPES_WITH_CREATED_HEADER.include?(issuable.work_item_type.base_type) - output << content_tag(:span, sprite_icon("#{issuable.work_item_type.icon_name}", css_class: 'gl-icon gl-vertical-align-middle'), class: 'gl-mr-2', aria: { hidden: 'true' }) + output << content_tag(:span, sprite_icon("#{issuable.work_item_type.icon_name}", css_class: 'gl-icon gl-vertical-align-middle gl-text-gray-500'), class: 'gl-mr-2', aria: { hidden: 'true' }) output << s_('IssuableStatus|%{wi_type} created %{created_at} by ').html_safe % { wi_type: issuable.issue_type.capitalize, created_at: time_ago_with_tooltip(issuable.created_at) } else output << s_('IssuableStatus|Created %{created_at} by').html_safe % { created_at: time_ago_with_tooltip(issuable.created_at) } @@ -240,6 +240,7 @@ module IssuablesHelper updateEndpoint: "#{issuable_path(issuable)}.json", canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable), canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable), + canUpdateTimelineEvent: can?(current_user, :admin_incident_management_timeline_event, issuable), issuableRef: issuable.to_reference, markdownPreviewPath: preview_markdown_path(parent, target_type: issuable.model_name, target_id: issuable.iid), markdownDocsPath: help_page_path('user/markdown'), diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb deleted file mode 100644 index 7cb6da26236..00000000000 --- a/app/helpers/javascript_helper.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module JavascriptHelper - def page_specific_javascript_tag(js) - javascript_include_tag asset_path(js) - end -end diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb index 4ddfb0224d1..0971fdae8dd 100644 --- a/app/helpers/jira_connect_helper.rb +++ b/app/helpers/jira_connect_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module JiraConnectHelper - def jira_connect_app_data(subscriptions) + def jira_connect_app_data(subscriptions, installation) skip_groups = subscriptions.map(&:namespace_id) { @@ -11,14 +11,16 @@ module JiraConnectHelper subscriptions_path: jira_connect_subscriptions_path(format: :json), 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, - oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? jira_connect_oauth_data.to_json : nil + oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? jira_connect_oauth_data(installation).to_json : nil } end private - def jira_connect_oauth_data - oauth_authorize_url = oauth_authorization_url( + def jira_connect_oauth_data(installation) + oauth_instance_url = installation.oauth_authorization_url + + oauth_authorize_path = oauth_authorization_path( client_id: Gitlab::CurrentSettings.jira_connect_application_key, response_type: 'code', scope: 'api', @@ -27,8 +29,8 @@ module JiraConnectHelper ) { - oauth_authorize_url: oauth_authorize_url, - oauth_token_url: oauth_token_url, + oauth_authorize_url: Gitlab::Utils.append_path(oauth_instance_url, oauth_authorize_path), + oauth_token_path: oauth_token_path, state: oauth_state, oauth_token_payload: { grant_type: :authorization_code, diff --git a/app/helpers/kerberos_spnego_helper.rb b/app/helpers/kerberos_helper.rb index 0f6812bc31b..31166772367 100644 --- a/app/helpers/kerberos_spnego_helper.rb +++ b/app/helpers/kerberos_helper.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -module KerberosSpnegoHelper +module KerberosHelper def allow_basic_auth? true # different behavior in GitLab Enterprise Edition end - def allow_kerberos_spnego_auth? + def allow_kerberos_auth? false # different behavior in GitLab Enterprise Edition end end -KerberosSpnegoHelper.prepend_mod_with('KerberosSpnegoHelper') +KerberosHelper.prepend_mod_with('KerberosHelper') diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index e865db128c1..0123eb68c9a 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -87,7 +87,7 @@ module LabelsHelper '#013220' => s_('SuggestedColors|Dark green'), '#6699cc' => s_('SuggestedColors|Blue-gray'), '#0000ff' => s_('SuggestedColors|Blue'), - '#e6e6fa' => s_('SuggestedColors|Lavendar'), + '#e6e6fa' => s_('SuggestedColors|Lavender'), '#9400d3' => s_('SuggestedColors|Dark violet'), '#330066' => s_('SuggestedColors|Deep violet'), '#808080' => s_('SuggestedColors|Gray'), diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index 421cf84f98c..a07922e451a 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -21,8 +21,8 @@ module LearnGitlabHelper end def learn_gitlab_onboarding_available?(project) - OnboardingProgress.onboarding?(project.namespace) && - LearnGitlab::Project.new(current_user).available? + Onboarding::Progress.onboarding?(project.namespace) && + Onboarding::LearnGitlab.new(current_user).available? end private @@ -33,10 +33,12 @@ module LearnGitlabHelper action_urls(project).to_h do |action, url| [ action, - url: url, - completed: attributes[OnboardingProgress.column_name(action)].present?, - svg: image_path("learn_gitlab/#{action}.svg"), - enabled: true + { + url: url, + completed: attributes[Onboarding::Progress.column_name(action)].present?, + svg: image_path("learn_gitlab/#{action}.svg"), + enabled: true + } ] end end @@ -70,11 +72,14 @@ module LearnGitlabHelper end def action_issue_urls - LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) } + Onboarding::Completion::ACTION_ISSUE_IDS.transform_values do |id| + project_issue_url(learn_gitlab_project, id) + end end def deploy_section_action_urls(project) - experiment(:security_actions_continuous_onboarding, + experiment( + :security_actions_continuous_onboarding, namespace: project.namespace, user: current_user, sticky_to: current_user @@ -91,11 +96,11 @@ module LearnGitlabHelper end def learn_gitlab_project - @learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project + @learn_gitlab_project ||= Onboarding::LearnGitlab.new(current_user).project end def onboarding_progress(project) - OnboardingProgress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord + Onboarding::Progress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 4581da4a063..45ded6e35d8 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -256,6 +256,26 @@ module MergeRequestsHelper def moved_mr_sidebar_enabled? Feature.enabled?(:moved_mr_sidebar, @project) && defined?(@merge_request) end + + def sticky_header_data + data = { + iid: @merge_request.iid, + projectPath: @project.full_path, + title: markdown_field(@merge_request, :title), + isFluidLayout: fluid_layout.to_s, + tabs: [ + ['show', _('Overview'), project_merge_request_path(@project, @merge_request), @merge_request.related_notes.user.count], + ['commits', _('Commits'), commits_project_merge_request_path(@project, @merge_request), @commits_count], + ['diffs', _('Changes'), diffs_project_merge_request_path(@project, @merge_request), @diffs_count] + ] + } + + if @project.builds_enabled? + data[:tabs].insert(2, ['pipelines', _('Pipelines'), pipelines_project_merge_request_path(@project, @merge_request), @number_of_pipelines]) + end + + data + end end MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper') diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index dc7d8049556..b017c9a81d1 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -135,7 +135,7 @@ module Nav id: 'general_new_group', title: _('New group'), href: new_group_path, - data: { track_action: 'click_link_new_group', track_label: 'plus_menu_dropdown' } + data: { track_action: 'click_link_new_group', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_group_link' } ) ) end diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index efec6f2d0d8..32d3f4aebb4 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -48,6 +48,13 @@ module Nav private + def top_nav_localized_headers + { + explore: s_('TopNav|Explore'), + switch_to: s_('TopNav|Switch to') + }.freeze + end + def build_base_view_model(builder:, project:, group:) if current_user build_view_model(builder: builder, project: project, group: group) @@ -60,6 +67,7 @@ module Nav # These come from `app/views/layouts/nav/_explore.html.ham` if explore_nav_link?(:projects) builder.add_primary_menu_item_with_shortcut( + header: top_nav_localized_headers[:explore], href: explore_root_path, active: nav == 'project' || active_nav_link?(path: %w[dashboard#show root#show projects#trending projects#starred projects#index]), **projects_menu_item_attrs @@ -68,6 +76,7 @@ module Nav if explore_nav_link?(:groups) builder.add_primary_menu_item_with_shortcut( + header: top_nav_localized_headers[:explore], href: explore_groups_path, active: nav == 'group' || active_nav_link?(controller: [:groups, 'groups/milestones', 'groups/group_members']), **groups_menu_item_attrs @@ -76,6 +85,7 @@ module Nav if explore_nav_link?(:snippets) builder.add_primary_menu_item_with_shortcut( + header: top_nav_localized_headers[:explore], active: active_nav_link?(controller: :snippets), href: explore_snippets_path, **snippets_menu_item_attrs @@ -89,6 +99,7 @@ module Nav current_item = project ? current_project(project: project) : {} builder.add_primary_menu_item_with_shortcut( + header: top_nav_localized_headers[:switch_to], active: nav == 'project' || active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]), css_class: 'qa-projects-dropdown', data: { track_label: "projects_dropdown", track_action: "click_dropdown" }, @@ -103,6 +114,7 @@ module Nav current_item = group ? current_group(group: group) : {} builder.add_primary_menu_item_with_shortcut( + header: top_nav_localized_headers[:switch_to], active: nav == 'group' || active_nav_link?(path: %w[dashboard/groups explore/groups]), css_class: 'qa-groups-dropdown', data: { track_label: "groups_dropdown", track_action: "click_dropdown" }, @@ -116,6 +128,7 @@ module Nav if dashboard_nav_link?(:milestones) builder.add_primary_menu_item_with_shortcut( id: 'milestones', + header: top_nav_localized_headers[:explore], title: _('Milestones'), href: dashboard_milestones_path, active: active_nav_link?(controller: 'dashboard/milestones'), @@ -127,6 +140,7 @@ module Nav if dashboard_nav_link?(:snippets) builder.add_primary_menu_item_with_shortcut( + header: top_nav_localized_headers[:explore], active: active_nav_link?(controller: 'dashboard/snippets'), data: { qa_selector: 'snippets_link', **menu_data_tracking_attrs('snippets') }, href: dashboard_snippets_path, @@ -137,6 +151,7 @@ module Nav if dashboard_nav_link?(:activity) builder.add_primary_menu_item_with_shortcut( id: 'activity', + header: top_nav_localized_headers[:explore], title: _('Activity'), href: activity_dashboard_path, active: active_nav_link?(path: 'dashboard#activity'), @@ -266,52 +281,74 @@ module Nav end def projects_submenu_items(builder:) - # These project links come from `app/views/layouts/nav/projects_dropdown/_show.html.haml` - [ - { id: 'your', title: _('Your projects'), href: dashboard_projects_path }, - { id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path }, - { id: 'explore', title: _('Explore projects'), href: explore_root_path }, - { id: 'topics', title: _('Explore topics'), href: topics_explore_projects_path } - ].each do |item| + if Feature.enabled?(:remove_extra_primary_submenu_options) + title = _('View all projects') + builder.add_primary_menu_item( - **item, - data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } + id: 'your', + title: title, + href: dashboard_projects_path, + data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } ) - end + else + # These project links come from `app/views/layouts/nav/projects_dropdown/_show.html.haml` + [ + { id: 'your', title: _('Your projects'), href: dashboard_projects_path }, + { id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path }, + { id: 'explore', title: _('Explore projects'), href: explore_root_path }, + { id: 'topics', title: _('Explore topics'), href: topics_explore_projects_path } + ].each do |item| + builder.add_primary_menu_item( + **item, + data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } + ) + end - title = _('Create new project') + title = _('Create new project') - builder.add_secondary_menu_item( - id: 'create', - title: title, - href: new_project_path, - data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } - ) + builder.add_secondary_menu_item( + id: 'create', + title: title, + href: new_project_path, + data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } + ) + end end def groups_submenu # These group links come from `app/views/layouts/nav/groups_dropdown/_show.html.haml` builder = ::Gitlab::Nav::TopNavMenuBuilder.new - [ - { id: 'your', title: _('Your groups'), href: dashboard_groups_path }, - { id: 'explore', title: _('Explore groups'), href: explore_groups_path } - ].each do |item| - builder.add_primary_menu_item( - **item, - data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } - ) - end + if Feature.enabled?(:remove_extra_primary_submenu_options) + title = _('View all groups') - if current_user.can_create_group? - title = _('Create group') - - builder.add_secondary_menu_item( - id: 'create', + builder.add_primary_menu_item( + id: 'your', title: title, - href: new_group_path, + href: dashboard_groups_path, data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } ) + else + [ + { id: 'your', title: _('Your groups'), href: dashboard_groups_path }, + { id: 'explore', title: _('Explore groups'), href: explore_groups_path } + ].each do |item| + builder.add_primary_menu_item( + **item, + data: { qa_selector: 'menu_item_link', qa_title: item[:title], **menu_data_tracking_attrs(item[:title]) } + ) + end + + if current_user.can_create_group? + title = _('Create group') + + builder.add_secondary_menu_item( + id: 'create', + title: title, + href: new_group_path, + data: { qa_selector: 'menu_item_link', qa_title: title, **menu_data_tracking_attrs(title) } + ) + end end builder.build diff --git a/app/helpers/notify_helper.rb b/app/helpers/notify_helper.rb index c0ba93f4a30..b7ab1c2e2d1 100644 --- a/app/helpers/notify_helper.rb +++ b/app/helpers/notify_helper.rb @@ -20,4 +20,15 @@ module NotifyHelper (source.description || default_description).truncate(200, separator: ' ') end + + def merge_request_hash_param(merge_request, reviewer) + { + mr_highlight: '<span style="font-weight: 600;color:#333333;">'.html_safe, + highlight_end: '</span>'.html_safe, + mr_link: link_to(merge_request.to_reference, merge_request_url(merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none").html_safe, + reviewer_highlight: '<span>'.html_safe, + reviewer_avatar: content_tag(:img, nil, height: "24", src: avatar_icon_for_user(reviewer, 24, only_path: false), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar", class: "avatar").html_safe, + reviewer_link: link_to(reviewer.name, user_url(reviewer), style: "color:#333333;text-decoration:none;", class: "muted").html_safe + } + end end diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index b52357bc891..f9ec20bdd01 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -73,6 +73,7 @@ module PackagesHelper older_than_options: older_than_options.to_json, is_admin: current_user&.admin.to_s, admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), + project_settings_path: project_settings_packages_and_registries_path(@project), enable_historic_entries: container_expiration_policies_historic_entry_enabled?.to_s, help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'), show_cleanup_policy_link: show_cleanup_policy_link(@project).to_s, @@ -83,7 +84,8 @@ module PackagesHelper def settings_data cleanup_settings_data.merge( show_container_registry_settings: show_container_registry_settings(@project).to_s, - show_package_registry_settings: show_package_registry_settings(@project).to_s + show_package_registry_settings: show_package_registry_settings(@project).to_s, + cleanup_settings_path: cleanup_image_tags_project_settings_packages_and_registries_path(@project) ) end end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 0c057a29bec..c0665463706 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -150,6 +150,10 @@ module PageLayoutHelper css_class.join(' ') end + def full_content_class + "#{container_class} #{@content_class}" # rubocop:disable Rails/HelperInstanceVariable + end + def page_itemtype(itemtype = nil) if itemtype @page_itemtype = { itemscope: true, itemtype: itemtype } diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index 104026ff21e..bfe39bbc211 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -53,7 +53,7 @@ module ProfilesHelper # Overridden in EE::ProfilesHelper#ssh_key_expires_field_description def ssh_key_expires_field_description - s_('Profiles|Key becomes invalid on this date.') + s_('Profiles|Optional but recommended. If set, key becomes invalid on the specified date.') end # Overridden in EE::ProfilesHelper#ssh_key_expiration_policy_enabled? diff --git a/app/helpers/projects/google_cloud/cloudsql_helper.rb b/app/helpers/projects/google_cloud/cloudsql_helper.rb new file mode 100644 index 00000000000..0c24254d9b4 --- /dev/null +++ b/app/helpers/projects/google_cloud/cloudsql_helper.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +module Projects + module GoogleCloud + module CloudsqlHelper + # Sources: + # - https://cloud.google.com/sql/docs/postgres/instance-settings + # - https://cloud.google.com/sql/docs/mysql/instance-settings + # - https://cloud.google.com/sql/docs/sqlserver/instance-settings + + TIERS = [ + { value: 'db-custom-1-3840', label: '1 vCPU, 3840 MB RAM - Standard' }, + { value: 'db-custom-2-7680', label: '2 vCPU, 7680 MB RAM - Standard' }, + { value: 'db-custom-2-13312', label: '2 vCPU, 13312 MB RAM - High memory' }, + { value: 'db-custom-4-15360', label: '4 vCPU, 15360 MB RAM - Standard' }, + { value: 'db-custom-4-26624', label: '4 vCPU, 26624 MB RAM - High memory' }, + { value: 'db-custom-8-30720', label: '8 vCPU, 30720 MB RAM - Standard' }, + { value: 'db-custom-8-53248', label: '8 vCPU, 53248 MB RAM - High memory' }, + { value: 'db-custom-16-61440', label: '16 vCPU, 61440 MB RAM - Standard' }, + { value: 'db-custom-16-106496', label: '16 vCPU, 106496 MB RAM - High memory' }, + { value: 'db-custom-32-122880', label: '32 vCPU, 122880 MB RAM - Standard' }, + { value: 'db-custom-32-212992', label: '32 vCPU, 212992 MB RAM - High memory' }, + { value: 'db-custom-64-245760', label: '64 vCPU, 245760 MB RAM - Standard' }, + { value: 'db-custom-64-425984', label: '64 vCPU, 425984 MB RAM - High memory' }, + { value: 'db-custom-96-368640', label: '96 vCPU, 368640 MB RAM - Standard' }, + { value: 'db-custom-96-638976', label: '96 vCPU, 638976 MB RAM - High memory' } + ].freeze + + VERSIONS = { + postgres: [ + { value: 'POSTGRES_14', label: 'PostgreSQL 14' }, + { value: 'POSTGRES_13', label: 'PostgreSQL 13' }, + { value: 'POSTGRES_12', label: 'PostgreSQL 12' }, + { value: 'POSTGRES_11', label: 'PostgreSQL 11' }, + { value: 'POSTGRES_10', label: 'PostgreSQL 10' }, + { value: 'POSTGRES_9_6', label: 'PostgreSQL 9.6' } + ], + mysql: [ + { value: 'MYSQL_8_0', label: 'MySQL 8' }, + { value: 'MYSQL_5_7', label: 'MySQL 5.7' }, + { value: 'MYSQL_5_6', label: 'MySQL 5.6' } + ], + sqlserver: [ + { value: 'SQLSERVER_2017_STANDARD', label: 'SQL Server 2017 Standard' }, + { value: 'SQLSERVER_2017_ENTERPRISE', label: 'SQL Server 2017 Enterprise' }, + { value: 'SQLSERVER_2017_EXPRESS', label: 'SQL Server 2017 Express' }, + { value: 'SQLSERVER_2017_WEB', label: 'SQL Server 2017 Web' }, + { value: 'SQLSERVER_2019_STANDARD', label: 'SQL Server 2019 Standard' }, + { value: 'SQLSERVER_2019_ENTERPRISE', label: 'SQL Server 2019 Enterprise' }, + { value: 'SQLSERVER_2019_EXPRESS', label: 'SQL Server 2019 Express' }, + { value: 'SQLSERVER_2019_WEB', label: 'SQL Server 2019 Web' } + ] + }.freeze + end + end +end diff --git a/app/helpers/projects/pages_helper.rb b/app/helpers/projects/pages_helper.rb new file mode 100644 index 00000000000..f46c11db1db --- /dev/null +++ b/app/helpers/projects/pages_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Projects + module PagesHelper + def can_create_pages_custom_domains?(current_user, project) + current_user.can?(:update_pages, project) && + (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https) && + project.can_create_custom_domains? + end + end +end diff --git a/app/helpers/projects/pipeline_helper.rb b/app/helpers/projects/pipeline_helper.rb index 5f2a9f7bf21..c72beb4d722 100644 --- a/app/helpers/projects/pipeline_helper.rb +++ b/app/helpers/projects/pipeline_helper.rb @@ -6,7 +6,6 @@ module Projects def js_pipeline_tabs_data(project, pipeline, _user) { - can_generate_codequality_reports: pipeline.can_generate_codequality_reports?.to_json, failed_jobs_count: pipeline.failed_builds.count, failed_jobs_summary: prepare_failed_jobs_summary_data(pipeline.failed_builds), full_path: project.full_path, diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index dfc270adf8b..e760fad7be9 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -172,6 +172,7 @@ module ProjectsHelper def project_list_cache_key(project, pipeline_status: true) key = [ + project.star_count, project.route.cache_key, project.cache_key, project.last_activity_date, @@ -389,7 +390,10 @@ module ProjectsHelper pagesAccessControlForced: ::Gitlab::Pages.access_control_is_forced?, pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control'), issuesHelpPath: help_page_path('user/project/issues/index'), - membersPagePath: project_project_members_path(project) + membersPagePath: project_project_members_path(project), + environmentsHelpPath: help_page_path('ci/environments/index'), + featureFlagsHelpPath: help_page_path('operations/feature_flags'), + releasesHelpPath: help_page_path('user/project/releases/index') } end @@ -437,7 +441,6 @@ module ProjectsHelper def show_inactive_project_deletion_banner?(project) return false unless project.present? && project.saved? return false unless delete_inactive_projects? - return false unless Feature.enabled?(:inactive_projects_deletion, project.root_namespace) project.inactive? end @@ -452,9 +455,9 @@ module ProjectsHelper def clusters_deprecation_alert_message if has_active_license? - s_('ClusterIntegration|The certificate-based Kubernetes integration has been deprecated and will be turned off at the end of November 2022. Please %{linkStart}migrate to the GitLab agent for Kubernetes%{linkEnd} or reach out to GitLab support.') + s_('ClusterIntegration|The certificate-based Kubernetes integration has been deprecated and will be turned off at the end of February 2023. Please %{linkStart}migrate to the GitLab agent for Kubernetes%{linkEnd} or reach out to GitLab support.') else - s_('ClusterIntegration|The certificate-based Kubernetes integration has been deprecated and will be turned off at the end of November 2022. Please %{linkStart}migrate to the GitLab agent for Kubernetes%{linkEnd}.') + s_('ClusterIntegration|The certificate-based Kubernetes integration has been deprecated and will be turned off at the end of February 2023. Please %{linkStart}migrate to the GitLab agent for Kubernetes%{linkEnd}.') end end @@ -635,6 +638,7 @@ module ProjectsHelper emailsDisabled: project.emails_disabled?, metricsDashboardAccessLevel: feature.metrics_dashboard_access_level, operationsAccessLevel: feature.operations_access_level, + monitorAccessLevel: feature.monitor_access_level, showDefaultAwardEmojis: project.show_default_award_emojis?, warnAboutPotentiallyUnwantedCharacters: project.warn_about_potentially_unwanted_characters?, enforceAuthChecksOnUploads: project.enforce_auth_checks_on_uploads?, diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index dc53be330fe..b16235893ae 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -239,26 +239,26 @@ module SearchHelper if can?(current_user, :download_code, @project) result.concat([ - { category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) }, - { category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) } - ]) + { category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) }, + { category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) } + ]) end if can?(current_user, :read_repository_graphs, @project) result.concat([ - { category: "In this project", label: _("Network"), url: project_network_path(@project, ref) }, - { category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) } - ]) + { category: "In this project", label: _("Network"), url: project_network_path(@project, ref) }, + { category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) } + ]) end result.concat([ - { category: "In this project", label: _("Issues"), url: project_issues_path(@project) }, - { category: "In this project", label: _("Merge requests"), url: project_merge_requests_path(@project) }, - { category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) }, - { category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) }, - { category: "In this project", label: _("Members"), url: project_project_members_path(@project) }, - { category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) } - ]) + { category: "In this project", label: _("Issues"), url: project_issues_path(@project) }, + { category: "In this project", label: _("Merge requests"), url: project_merge_requests_path(@project) }, + { category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) }, + { category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) }, + { category: "In this project", label: _("Members"), url: project_project_members_path(@project) }, + { category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) } + ]) if can?(current_user, :read_feature_flag, @project) result << { category: "In this project", label: _("Feature Flags"), url: project_feature_flags_path(@project) } @@ -294,13 +294,13 @@ module SearchHelper return [] unless issue && Ability.allowed?(current_user, :read_issue, issue) [ - { - category: 'In this project', - id: issue.id, - label: search_result_sanitize("#{issue.title} (#{issue.to_reference})"), - url: issue_path(issue), - avatar_url: issue.project.avatar_url || '' - } + { + category: 'In this project', + id: issue.id, + label: search_result_sanitize("#{issue.title} (#{issue.to_reference})"), + url: issue_path(issue), + avatar_url: issue.project.avatar_url || '' + } ] end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 58f0af883f5..a711f36fe05 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -157,7 +157,9 @@ module SortingHelper { sort_value_name => sort_title_name, sort_value_oldest_updated => sort_title_oldest_updated, - sort_value_recently_updated => sort_title_recently_updated + sort_value_recently_updated => sort_title_recently_updated, + sort_value_version_desc => sort_title_version_desc, + sort_value_version_asc => sort_title_version_asc } end diff --git a/app/helpers/sorting_titles_values_helper.rb b/app/helpers/sorting_titles_values_helper.rb index 4dfa7689110..b49cb617d80 100644 --- a/app/helpers/sorting_titles_values_helper.rb +++ b/app/helpers/sorting_titles_values_helper.rb @@ -86,6 +86,14 @@ module SortingTitlesValuesHelper s_('SortOptions|Name, descending') end + def sort_title_version_desc + s_('SortOptions|Latest version') + end + + def sort_title_version_asc + s_('SortOptions|Oldest version') + end + def sort_title_oldest_activity s_('SortOptions|Oldest updated') end @@ -275,6 +283,14 @@ module SortingTitlesValuesHelper 'updated_asc' end + def sort_value_version_asc + 'version_asc' + end + + def sort_value_version_desc + 'version_desc' + end + def sort_value_popularity 'popularity' end diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb index 9e516d726c1..a60143db739 100644 --- a/app/helpers/storage_helper.rb +++ b/app/helpers/storage_helper.rb @@ -23,119 +23,4 @@ module StorageHelper _("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 - - def storage_enforcement_banner_info(context) - root_ancestor = context.root_ancestor - - return unless should_show_storage_enforcement_banner?(context, current_user, root_ancestor) - - text_args = storage_enforcement_banner_text_args(root_ancestor, context) - - text_paragraph_2 = if root_ancestor.user_namespace? - html_escape_once(s_("UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. " \ - "View and manage your usage from %{strong_start}User settings > Usage quotas%{strong_end}. %{docs_link_start}Learn more%{link_end} " \ - "about how to reduce your storage.")).html_safe % text_args[:p2] - else - html_escape_once(s_("UsageQuota|The namespace is currently using %{strong_start}%{used_storage}%{strong_end} of namespace storage. " \ - "Group owners can view namespace storage usage and purchase more from %{strong_start}Group settings > Usage quotas%{strong_end}. %{docs_link_start}Learn more.%{link_end}" \ - )).html_safe % text_args[:p2] - end - - { - text_paragraph_1: html_escape_once(s_("UsageQuota|Effective %{storage_enforcement_date}, namespace storage limits will apply " \ - "to the %{strong_start}%{namespace_name}%{strong_end} namespace. %{extra_message}" \ - "View the %{rollout_link_start}rollout schedule for this change%{link_end}.")).html_safe % text_args[:p1], - text_paragraph_2: text_paragraph_2, - text_paragraph_3: html_escape_once(s_("UsageQuota|See our %{faq_link_start}FAQ%{link_end} for more information.")).html_safe % text_args[:p3], - variant: 'warning', - namespace_id: root_ancestor.id, - callouts_path: root_ancestor.user_namespace? ? callouts_path : group_callouts_path, - callouts_feature_name: storage_enforcement_banner_user_callouts_feature_name(root_ancestor) - } - end - - private - - def should_show_storage_enforcement_banner?(context, current_user, root_ancestor) - return false unless user_allowed_storage_enforcement_banner?(context, current_user, root_ancestor) - return false if root_ancestor.paid? - return false unless future_enforcement_date?(root_ancestor) - return false if user_dismissed_storage_enforcement_banner?(root_ancestor) - - ::Feature.enabled?(:namespace_storage_limit_show_preenforcement_banner, root_ancestor) - end - - def user_allowed_storage_enforcement_banner?(context, current_user, root_ancestor) - return can?(current_user, :maintainer_access, context) unless context.respond_to?(:user_namespace?) && context.user_namespace? - - can?(current_user, :owner_access, context) - end - - def storage_enforcement_banner_text_args(root_ancestor, context) - strong_tags = { - strong_start: "<strong>".html_safe, - strong_end: "</strong>".html_safe - } - - extra_message = if context.is_a?(Project) - html_escape_once(s_("UsageQuota|The %{strong_start}%{context_name}%{strong_end} project will be affected by this. ")) - .html_safe % strong_tags.merge(context_name: context.name) - elsif !context.root? - html_escape_once(s_("UsageQuota|The %{strong_start}%{context_name}%{strong_end} group will be affected by this. ")) - .html_safe % strong_tags.merge(context_name: context.name) - else - '' - end - - { - p1: { - storage_enforcement_date: root_ancestor.storage_enforcement_date, - namespace_name: root_ancestor.name, - extra_message: extra_message, - rollout_link_start: '<a href="%{url}" >'.html_safe % { url: help_page_path('user/usage_quotas', anchor: 'namespace-storage-limit-enforcement-schedule') }, - link_end: "</a>".html_safe - }.merge(strong_tags), - p2: { - used_storage: storage_counter(root_ancestor.root_storage_statistics&.storage_size || 0), - docs_link_start: '<a href="%{url}" >'.html_safe % { url: help_page_path('user/usage_quotas', anchor: 'manage-your-storage-usage') }, - link_end: "</a>".html_safe - }.merge(strong_tags), - p3: { - faq_link_start: '<a href="%{url}" >'.html_safe % { url: "#{Gitlab::Saas.about_pricing_url}faq-efficient-free-tier/#storage-limits-on-gitlab-saas-free-tier" }, - link_end: "</a>".html_safe - } - } - end - - def storage_enforcement_banner_user_callouts_feature_name(namespace) - "storage_enforcement_banner_#{storage_enforcement_banner_threshold(namespace)}_enforcement_threshold" - end - - def storage_enforcement_banner_threshold(namespace) - days_to_enforcement_date = (namespace.storage_enforcement_date - Date.today) - - return :first if days_to_enforcement_date > 30 - return :second if days_to_enforcement_date > 15 && days_to_enforcement_date <= 30 - return :third if days_to_enforcement_date > 7 && days_to_enforcement_date <= 15 - return :fourth if days_to_enforcement_date >= 0 && days_to_enforcement_date <= 7 - end - - def user_dismissed_storage_enforcement_banner?(namespace) - return false unless current_user - - if namespace.user_namespace? - current_user.dismissed_callout?(feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace)) - else - current_user.dismissed_callout_for_group?( - feature_name: storage_enforcement_banner_user_callouts_feature_name(namespace), - group: namespace - ) - end - end - - def future_enforcement_date?(namespace) - return true if ::Feature.enabled?(:namespace_storage_limit_bypass_date_check, namespace) - - namespace.storage_enforcement_date.present? && namespace.storage_enforcement_date >= Date.today - end end diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index a957c9ce9e0..3e5f63796b2 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -45,7 +45,11 @@ module SystemNoteHelper 'attention_requested' => 'user', 'attention_request_removed' => 'user', 'contact' => 'users', - 'timeline_event' => 'clock' + 'timeline_event' => 'clock', + 'relate_to_child' => 'link', + 'unrelate_from_child' => 'link', + 'relate_to_parent' => 'link', + 'unrelate_from_parent' => 'link' }.freeze def system_note_icon_name(note) diff --git a/app/helpers/timeboxes_helper.rb b/app/helpers/timeboxes_helper.rb index 39993bbfb44..11d09a79dcf 100644 --- a/app/helpers/timeboxes_helper.rb +++ b/app/helpers/timeboxes_helper.rb @@ -172,18 +172,19 @@ module TimeboxesHelper def timebox_date_range(timebox) if timebox.start_date && timebox.due_date - "#{timebox.start_date.to_s(:medium)}–#{timebox.due_date.to_s(:medium)}" + s_("DateRange|%{start_date}–%{end_date}") % { start_date: l(timebox.start_date, format: Date::DATE_FORMATS[:medium]), + end_date: l(timebox.due_date, format: Date::DATE_FORMATS[:medium]) } elsif timebox.due_date if timebox.due_date.past? - _("expired on %{timebox_due_date}") % { timebox_due_date: timebox.due_date.to_s(:medium) } + _("expired on %{timebox_due_date}") % { timebox_due_date: l(timebox.due_date, format: Date::DATE_FORMATS[:medium]) } else - _("expires on %{timebox_due_date}") % { timebox_due_date: timebox.due_date.to_s(:medium) } + _("expires on %{timebox_due_date}") % { timebox_due_date: l(timebox.due_date, format: Date::DATE_FORMATS[:medium]) } end elsif timebox.start_date if timebox.start_date.past? - _("started on %{timebox_start_date}") % { timebox_start_date: timebox.start_date.to_s(:medium) } + _("started on %{timebox_start_date}") % { timebox_start_date: l(timebox.start_date, format: Date::DATE_FORMATS[:medium]) } else - _("starts on %{timebox_start_date}") % { timebox_start_date: timebox.start_date.to_s(:medium) } + _("starts on %{timebox_start_date}") % { timebox_start_date: l(timebox.start_date, format: Date::DATE_FORMATS[:medium]) } end end end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 5977f51cab1..ecf29c41100 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -142,6 +142,16 @@ module TodosHelper todos_filter_params.values.none? end + def no_todos_messages + [ + s_('Todos|Good job! Looks like you don\'t have anything left on your To-Do List'), + s_('Todos|Isn\'t an empty To-Do List beautiful?'), + s_('Todos|Give yourself a pat on the back!'), + s_('Todos|Nothing left to do. High five!'), + s_('Todos|Henceforth, you shall be known as "To-Do Destroyer"') + ] + end + def todos_filter_path(options = {}) without = options.delete(:without) diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb index 3dd6b3f4a80..d8baa185370 100644 --- a/app/helpers/users/callouts_helper.rb +++ b/app/helpers/users/callouts_helper.rb @@ -10,8 +10,10 @@ module Users REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout' UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout' SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout' + MERGE_REQUEST_SETTINGS_MOVED_CALLOUT = 'merge_request_settings_moved_callout' REGISTRATION_ENABLED_CALLOUT_ALLOWED_CONTROLLER_PATHS = [/^root/, /^dashboard\S*/, /^admin\S*/].freeze WEB_HOOK_DISABLED = 'web_hook_disabled' + ULTIMATE_FEATURE_REMOVAL_BANNER = 'ultimate_feature_removal_banner' def show_gke_cluster_integration_callout?(project) active_nav_link?(controller: sidebar_operations_paths) && @@ -71,18 +73,28 @@ module Users last_failure = DateTime.parse(last_failure) if last_failure - user_dismissed?(WEB_HOOK_DISABLED, last_failure, namespace: project.namespace) + user_dismissed?(WEB_HOOK_DISABLED, last_failure, project: project) + end + + def show_merge_request_settings_callout? + !user_dismissed?(MERGE_REQUEST_SETTINGS_MOVED_CALLOUT) + end + + def ultimate_feature_removal_banner_dismissed?(project) + return false unless project + + user_dismissed?(ULTIMATE_FEATURE_REMOVAL_BANNER, project: project) end private - def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil, namespace: nil) + def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil, project: nil) return false unless current_user query = { feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than } - if namespace - current_user.dismissed_callout_for_namespace?(namespace: namespace, **query) + if project + current_user.dismissed_callout_for_project?(project: project, **query) else current_user.dismissed_callout?(**query) end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index cae2addea9c..271fa47dd97 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -18,10 +18,11 @@ module UsersHelper return _('We also use email for avatar detection if no avatar is uploaded.') unless user.unconfirmed_email.present? confirmation_link = link_to _('Resend confirmation e-mail'), user_confirmation_path(user: { email: user.unconfirmed_email }), method: :post - - h(_('Please click the link in the confirmation email before continuing. It was sent to ')) + - content_tag(:strong) { user.unconfirmed_email } + h('.') + - content_tag(:p) { confirmation_link } + h(_('Please click the link in the confirmation email before continuing. It was sent to %{html_tag_strong_start}%{email}%{html_tag_strong_end}.')) % { + html_tag_strong_start: '<strong>'.html_safe, + html_tag_strong_end: '</strong>'.html_safe, + email: user.unconfirmed_email + } + content_tag(:p) { confirmation_link } end def profile_tabs @@ -93,6 +94,7 @@ module UsersHelper [].tap do |badges| badges << blocked_user_badge(user) if user.blocked? badges << { text: s_('AdminUsers|Admin'), variant: 'success' } if user.admin? + badges << { text: s_('AdminUsers|Bot'), variant: 'muted' } if user.bot? badges << { text: s_('AdminUsers|External'), variant: 'secondary' } if user.external? badges << { text: s_("AdminUsers|It's you!"), variant: 'muted' } if current_user == user badges << { text: s_("AdminUsers|Locked"), variant: 'warning' } if user.access_locked? @@ -197,6 +199,9 @@ module UsersHelper banned_badge = { text: s_('AdminUsers|Banned'), variant: 'danger' } return banned_badge if user.banned? + ldap_blocked_badge = { text: s_('AdminUsers|LDAP Blocked'), variant: 'danger' } + return ldap_blocked_badge if user.ldap_blocked? + { text: s_('AdminUsers|Blocked'), variant: 'danger' } end diff --git a/app/helpers/web_hooks/web_hooks_helper.rb b/app/helpers/web_hooks/web_hooks_helper.rb index 95122750c2f..e95b90c69ef 100644 --- a/app/helpers/web_hooks/web_hooks_helper.rb +++ b/app/helpers/web_hooks/web_hooks_helper.rb @@ -5,6 +5,7 @@ module WebHooks EXPIRY_TTL = 1.hour def show_project_hook_failed_callout?(project:) + return false if project_hook_page? return false unless current_user return false unless Feature.enabled?(:webhooks_failed_callout, project) return false unless Feature.enabled?(:web_hooks_disable_failed, project) @@ -23,5 +24,9 @@ module WebHooks ProjectHook.for_projects(project).disabled.exists? end end + + def project_hook_page? + current_controller?('projects/hooks') || current_controller?('projects/hook_logs') + end end end |