diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-20 18:19:03 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-20 18:19:03 +0300 |
commit | 14bd84b61276ef29b97d23642d698de769bacfd2 (patch) | |
tree | f9eba90140c1bd874211dea17750a0d422c04080 /app/helpers | |
parent | 891c388697b2db0d8ee0c8358a9bdbf6dc56d581 (diff) |
Add latest changes from gitlab-org/gitlab@15-10-stable-eev15.10.0-rc42
Diffstat (limited to 'app/helpers')
50 files changed, 609 insertions, 276 deletions
diff --git a/app/helpers/admin/abuse_reports_helper.rb b/app/helpers/admin/abuse_reports_helper.rb new file mode 100644 index 00000000000..3218ecfd1db --- /dev/null +++ b/app/helpers/admin/abuse_reports_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Admin + module AbuseReportsHelper + def abuse_reports_list_data(reports) + { + abuse_reports_data: { + categories: AbuseReport.categories.keys, + reports: Admin::AbuseReportSerializer.new.represent(reports), + pagination: { + current_page: reports.current_page, + per_page: reports.limit_value, + total_items: reports.total_count + } + }.to_json + } + end + end +end diff --git a/app/helpers/analytics/cycle_analytics_helper.rb b/app/helpers/analytics/cycle_analytics_helper.rb deleted file mode 100644 index 35a5d4f469d..00000000000 --- a/app/helpers/analytics/cycle_analytics_helper.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Analytics - module CycleAnalyticsHelper - def cycle_analytics_default_stage_config - Gitlab::Analytics::CycleAnalytics::DefaultStages.all.map do |stage_params| - Analytics::CycleAnalytics::StagePresenter.new(stage_params) - end - end - - def cycle_analytics_initial_data(project, group = nil) - base_data = { project_id: project.id, group_path: project.group&.path, request_path: project_cycle_analytics_path(project), full_path: project.full_path } - svgs = { empty_state_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_data_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_access_svg_path: image_path("illustrations/analytics/no-access.svg") } - api_paths = group.present? ? cycle_analytics_group_api_paths(group) : cycle_analytics_project_api_paths(project) - - base_data.merge(svgs, api_paths) - end - - private - - def cycle_analytics_group_api_paths(group) - { milestones_path: group_milestones_path(group, format: :json), labels_path: group_labels_path(group, format: :json), group_path: group_path(group), group_id: group&.id } - end - - def cycle_analytics_project_api_paths(project) - { milestones_path: project_milestones_path(project, format: :json), labels_path: project_labels_path(project, format: :json), group_path: project.parent&.path, group_id: project.parent&.id } - end - end -end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 93b7c8c0b94..d0602952f9a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -181,14 +181,14 @@ module ApplicationHelper css_classes << html_class unless html_class.blank? content_tag :time, l(time, format: "%b %d, %Y"), - class: css_classes.join(' '), - title: l(time.to_time.in_time_zone, format: :timeago_tooltip), - datetime: time.to_time.getutc.iso8601, - data: { - toggle: 'tooltip', - placement: placement, - container: 'body' - } + class: css_classes.join(' '), + title: l(time.to_time.in_time_zone, format: :timeago_tooltip), + datetime: time.to_time.getutc.iso8601, + data: { + toggle: 'tooltip', + placement: placement, + container: 'body' + } end def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', exclude_author: false) @@ -200,7 +200,7 @@ module ApplicationHelper if !exclude_author && object.last_edited_by output << content_tag(:span, ' by ') - output << link_to_member(object.project, object.last_edited_by, avatar: false, author_class: nil) + output << link_to_member(object.project, object.last_edited_by, avatar: false, extra_class: 'gl-hover-text-decoration-underline', author_class: nil) end output @@ -374,6 +374,10 @@ module ApplicationHelper cookies["sidebar_collapsed"] == "true" end + def collapsed_super_sidebar? + cookies["super_sidebar_collapsed"] == "true" + end + def locale_path asset_path("locale/#{Gitlab::I18n.locale}/app.js") end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 3abaae98c29..fd684ee5ecb 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -4,11 +4,11 @@ module ApplicationSettingsHelper extend self delegate :allow_signup?, - :gravatar_enabled?, - :password_authentication_enabled_for_web?, - :akismet_enabled?, - :spam_check_endpoint_enabled?, - to: :'Gitlab::CurrentSettings.current_application_settings' + :gravatar_enabled?, + :password_authentication_enabled_for_web?, + :akismet_enabled?, + :spam_check_endpoint_enabled?, + to: :'Gitlab::CurrentSettings.current_application_settings' def user_oauth_applications? Gitlab::CurrentSettings.user_oauth_applications @@ -248,7 +248,9 @@ module ApplicationSettingsHelper :default_project_visibility, :default_projects_limit, :default_snippet_visibility, + :default_syntax_highlighting_theme, :delete_inactive_projects, + :deny_all_requests_except_allowed, :disable_admin_oauth_scopes, :disable_feed_token, :disabled_oauth_sign_in_sources, @@ -401,6 +403,7 @@ module ApplicationSettingsHelper :protected_paths_raw, :time_tracking_limit_to_hours, :two_factor_grace_period, + :update_runner_versions_enabled, :unique_ips_limit_enabled, :unique_ips_limit_per_user, :unique_ips_limit_time_window, @@ -478,7 +481,9 @@ module ApplicationSettingsHelper :bulk_import_enabled, :allow_runner_registration_token, :user_defaults_to_private_profile, - :deactivation_email_additional_text + :deactivation_email_additional_text, + :projects_api_rate_limit_unauthenticated, + :gitlab_dedicated_instance ].tap do |settings| next if Gitlab.com? diff --git a/app/helpers/artifacts_helper.rb b/app/helpers/artifacts_helper.rb index df0432105d5..f90d59409ed 100644 --- a/app/helpers/artifacts_helper.rb +++ b/app/helpers/artifacts_helper.rb @@ -4,6 +4,7 @@ module ArtifactsHelper def artifacts_app_data(project) { project_path: project.full_path, + project_id: project.id, can_destroy_artifacts: can?(current_user, :destroy_artifacts, project).to_s, artifacts_management_feedback_image_path: image_path('illustrations/chat-bubble-sm.svg') } diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 281d5c923d0..bb6fd6c3dad 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -2,9 +2,7 @@ module BlobHelper def edit_blob_path(project = @project, ref = @ref, path = @path, options = {}) - project_edit_blob_path(project, - tree_join(ref, path), - options[:link_opts]) + project_edit_blob_path(project, tree_join(ref, path), options[:link_opts]) end def ide_edit_path(project = @project, ref = @ref, path = @path) @@ -52,9 +50,11 @@ module BlobHelper def fork_path_for_current_user(project, path, with_notice: true) return unless current_user - project_forks_path(project, - namespace_key: current_user.namespace&.id, - continue: edit_blob_fork_params(path, with_notice: with_notice)) + project_forks_path( + project, + namespace_key: current_user.namespace&.id, + continue: edit_blob_fork_params(path, with_notice: with_notice) + ) end def encode_ide_path(path) @@ -66,12 +66,14 @@ module BlobHelper common_classes = "btn gl-button btn-confirm js-edit-blob gl-ml-3 #{options[:extra_class]}" - edit_button_tag(blob, - common_classes, - _('Edit'), - edit_blob_path(project, ref, path, options), - project, - ref) + edit_button_tag( + blob, + common_classes, + _('Edit'), + edit_blob_path(project, ref, path, options), + project, + ref + ) end def can_modify_blob?(blob, project = @project, ref = @ref) @@ -282,8 +284,8 @@ module BlobHelper fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: params) button_tag label, - class: "#{common_classes} js-edit-blob-link-fork-toggler", - data: { action: action, fork_path: fork_path } + class: "#{common_classes} js-edit-blob-link-fork-toggler", + data: { action: action, fork_path: fork_path } end def edit_disabled_button_tag(button_text, common_classes) diff --git a/app/helpers/ci/catalog/resources_helper.rb b/app/helpers/ci/catalog/resources_helper.rb new file mode 100644 index 00000000000..46d78cd6b24 --- /dev/null +++ b/app/helpers/ci/catalog/resources_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Ci + module Catalog + module ResourcesHelper + def can_view_private_catalog?(_project) + false + end + + def js_ci_catalog_data + {} + end + end + end +end diff --git a/app/helpers/ci/pipeline_editor_helper.rb b/app/helpers/ci/pipeline_editor_helper.rb index 99a92ba9b59..4d1bdf5fa7f 100644 --- a/app/helpers/ci/pipeline_editor_helper.rb +++ b/app/helpers/ci/pipeline_editor_helper.rb @@ -18,12 +18,12 @@ module Ci "ci-examples-help-page-path" => help_page_path('ci/examples/index'), "ci-help-page-path" => help_page_path('ci/index'), "ci-lint-path" => project_ci_lint_path(project), + "ci-troubleshooting-path" => help_page_path('ci/troubleshooting', anchor: 'common-cicd-issues'), "default-branch" => project.default_branch_or_main, "empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'), "initial-branch-name" => initial_branch, "includes-help-page-path" => help_page_path('ci/yaml/includes'), "lint-help-page-path" => help_page_path('ci/lint', anchor: 'check-cicd-syntax'), - "lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available-message'), "needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'), "new-merge-request-path" => namespace_project_new_merge_request_path, "pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(latest_commit.sha) : '', diff --git a/app/helpers/ci/status_helper.rb b/app/helpers/ci/status_helper.rb index bca49324a19..ea5b613cb78 100644 --- a/app/helpers/ci/status_helper.rb +++ b/app/helpers/ci/status_helper.rb @@ -131,10 +131,10 @@ module Ci if path link_to ci_icon_for_status(status, size: icon_size), path, - class: klass, title: title, data: data + class: klass, title: title, data: data else content_tag :span, ci_icon_for_status(status, size: icon_size), - class: klass, title: title, data: data + class: klass, title: title, data: data end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index f75d3657986..519508f1c02 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -27,12 +27,11 @@ module CommitsHelper end def commit_to_html(commit, ref, project) - render partial: 'projects/commits/commit', formats: :html, - locals: { - commit: commit, - ref: ref, - project: project - } + render partial: 'projects/commits/commit', formats: :html, locals: { + commit: commit, + ref: ref, + project: project + } end # Breadcrumb links for a Project and, if applicable, a tree path @@ -161,17 +160,23 @@ module CommitsHelper # This includes a keyed hash for values that can be nil, to prevent invalid cache entries # being served if the order should change in future. def commit_partial_cache_key(commit, ref:, merge_request:, request:) + keyed_hash = { + merge_request: merge_request&.cache_key, + pipeline_status: commit.detailed_status_for(ref)&.cache_key, + xhr: request.xhr?, + controller: controller.controller_path, + path: @path # referred to in #link_to_browse_code + } + + if Feature.enabled?(:show_tags_on_commits_view, commit.project) + keyed_hash[:referenced_by] = tag_checksum(commit.referenced_by) + end + [ commit, commit.author, ref, - { - merge_request: merge_request&.cache_key, - pipeline_status: commit.detailed_status_for(ref)&.cache_key, - xhr: request.xhr?, - controller: controller.controller_path, - path: @path # referred to in #link_to_browse_code - } + keyed_hash ] end @@ -188,16 +193,22 @@ module CommitsHelper entity = mode == 'raw' ? 'rawButton' : 'renderedButton' title = "Display #{mode} diff" - link_to("##{mode}-diff-#{file_hash}", - class: "btn gl-button btn-default btn-file-option has-tooltip btn-show-#{mode}-diff", - title: title, - data: { file_hash: file_hash, diff_toggle_entity: entity }) do + link_to( + "##{mode}-diff-#{file_hash}", + class: "btn gl-button btn-default btn-file-option has-tooltip btn-show-#{mode}-diff", + title: title, + data: { file_hash: file_hash, diff_toggle_entity: entity } + ) do sprite_icon(icon) end end protected + def tag_checksum(tags_array) + ::Zlib.crc32(tags_array.sort.join) + end + # Private: Returns a link to a person. If the person has a matching user and # is a member of the current @project it will link to the team member page. # Otherwise it will link to the person email as specified in the commit. diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index f0e1f252917..0352f5a1dfc 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -40,9 +40,14 @@ module DashboardHelper end) if doc_href.present? - link_to_doc = link_to(sprite_icon('question'), doc_href, - class: 'gl-ml-2', title: _('Documentation'), - target: '_blank', rel: 'noopener noreferrer') + link_to_doc = link_to( + sprite_icon('question'), + doc_href, + class: 'gl-ml-2', + title: _('Documentation'), + target: '_blank', + rel: 'noopener noreferrer' + ) concat(link_to_doc) end @@ -52,7 +57,7 @@ module DashboardHelper private def get_dashboard_nav_links - links = [:projects, :groups, :snippets] + links = [:projects, :groups, :snippets, :your_work, :explore] if can?(current_user, :read_cross_project) links += [:activity, :milestones] diff --git a/app/helpers/device_registration_helper.rb b/app/helpers/device_registration_helper.rb new file mode 100644 index 00000000000..bbdcab76bf5 --- /dev/null +++ b/app/helpers/device_registration_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module DeviceRegistrationHelper + def device_registration_data(current_password_required:, target_path:, webauthn_error:) + { + initial_error: webauthn_error && webauthn_error[:message], + target_path: target_path, + password_required: current_password_required.to_s + } + end +end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index e0a1697cfa9..c5df53ec606 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -34,6 +34,12 @@ module DiffHelper options[:expanded] = true options[:paths] = params.values_at(:old_path, :new_path) options[:use_extra_viewer_as_main] = false + + if Feature.enabled?(:large_ipynb_diffs, @project) && params[:file_identifier]&.include?('.ipynb') + options[:max_patch_bytes_for_file_extension] = { + '.ipynb' => 1.megabyte + } + end end options diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 427cbe18fbf..475ba3dcba8 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -54,7 +54,7 @@ module DropdownsHelper default_label = data_attr[:default_label] content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") - output << sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3") + output << sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon") output.html_safe end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index bef2da495b0..795d35ec81f 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -29,10 +29,11 @@ module EventsHelper opened: s_('Event|opened'), updated: s_('Event|updated'), 'removed due to membership expiration from': s_('Event|removed due to membership expiration from') - }.merge(localized_push_action_name_map, - localized_created_project_action_name_map, - localized_design_action_names - ).freeze + }.merge( + localized_push_action_name_map, + localized_created_project_action_name_map, + localized_design_action_names + ).freeze end def localized_push_action_name_map @@ -183,13 +184,11 @@ module EventsHelper def event_feed_url(event) if event.issue? - project_issue_url(event.project, - event.issue) + project_issue_url(event.project, event.issue) elsif event.merge_request? project_merge_request_url(event.project, event.merge_request) elsif event.commit_note? - project_commit_url(event.project, - event.note_target) + project_commit_url(event.project, event.note_target) elsif event.note? if event.note_target event_note_target_url(event) @@ -204,16 +203,12 @@ module EventsHelper def push_event_feed_url(event) if event.push_with_commits? && event.md_ref? if event.commits_count > 1 - project_compare_url(event.project, - from: event.commit_from, to: - event.commit_to) + project_compare_url(event.project, from: event.commit_from, to: event.commit_to) else - project_commit_url(event.project, - id: event.commit_to) + project_commit_url(event.project, id: event.commit_to) end elsif event.ref_name - project_commits_url(event.project, - event.ref_name) + project_commits_url(event.project, event.ref_name) end end @@ -241,26 +236,31 @@ module EventsHelper elsif event.design_note? design_url(event.note_target, anchor: dom_id(event.note)) else - polymorphic_url([event.project, event.note_target], - anchor: dom_id(event.target)) + polymorphic_url([event.project, event.note_target], anchor: dom_id(event.target)) end end def event_wiki_title_html(event) capture do concat content_tag(:span, _('wiki page'), class: "event-target-type gl-mr-2") - concat link_to(event.target_title, event_wiki_page_target_url(event), - title: event.target_title, - class: 'has-tooltip event-target-link gl-mr-2') + concat link_to( + event.target_title, + event_wiki_page_target_url(event), + title: event.target_title, + class: 'has-tooltip event-target-link gl-mr-2' + ) end end def event_design_title_html(event) capture do concat content_tag(:span, _('design'), class: "event-target-type gl-mr-2") - concat link_to(event.design.reference_link_text, design_url(event.design), - title: event.target_title, - class: 'has-tooltip event-design event-target-link gl-mr-2') + concat link_to( + event.design.reference_link_text, + design_url(event.design), + title: event.target_title, + class: 'has-tooltip event-design event-target-link gl-mr-2' + ) end end diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb index 2967501f628..ed24f2509e8 100644 --- a/app/helpers/explore_helper.rb +++ b/app/helpers/explore_helper.rb @@ -57,7 +57,7 @@ module ExploreHelper private def get_explore_nav_links - [:projects, :groups, :snippets] + [:projects, :groups, :topics, :snippets] end def request_path_with_options(options = {}) diff --git a/app/helpers/feature_flags_helper.rb b/app/helpers/feature_flags_helper.rb index 3dde29dce91..fe8d8e6b5d9 100644 --- a/app/helpers/feature_flags_helper.rb +++ b/app/helpers/feature_flags_helper.rb @@ -18,8 +18,10 @@ module FeatureFlagsHelper feature_flags_path: project_feature_flags_path(@project), environments_endpoint: search_project_environments_path(@project, format: :json), strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'), - environments_scope_docs_path: help_page_path('ci/environments/index.md', - anchor: 'limit-the-environment-scope-of-a-cicd-variable') + environments_scope_docs_path: help_page_path( + 'ci/environments/index.md', + anchor: 'limit-the-environment-scope-of-a-cicd-variable' + ) } end end diff --git a/app/helpers/groups/observability_helper.rb b/app/helpers/groups/observability_helper.rb index 6cd6566cee1..7661817da7b 100644 --- a/app/helpers/groups/observability_helper.rb +++ b/app/helpers/groups/observability_helper.rb @@ -22,19 +22,8 @@ module Groups }.freeze def observability_iframe_src(group) - # Format: https://observe.gitlab.com/GROUP_ID - - # When running Observability UI in standalone mode (i.e. not backed by Observability Backend) - # the group-id is not required. This is mostly used for local dev - base_url = ENV['STANDALONE_OBSERVABILITY_UI'] == 'true' ? observability_url : "#{observability_url}/-/#{group.id}" - - sanitized_path = if params[:observability_path] && sanitize(params[:observability_path]) != '' - CGI.unescapeHTML(sanitize(params[:observability_path])) - else - observability_config_for(params).fetch(:path) - end - - "#{base_url}#{sanitized_path}" + Gitlab::Observability.build_full_url(group, params[:observability_path], + observability_config_for(params).fetch(:path)) end def observability_page_title @@ -43,10 +32,6 @@ module Groups private - def observability_url - Gitlab::Observability.observability_url - end - def observability_config_for(params) ACTION_TO_PATH.fetch(params[:action], ACTION_TO_PATH['dashboards']) end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 129871ca3fd..ce64ac1f21f 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -126,6 +126,7 @@ module GroupsHelper def subgroup_creation_data(group) { + parent_group_url: group.parent && group_url(group.parent), parent_group_name: group.parent&.name, import_existing_group_path: new_group_path(parent_id: group.parent_id, anchor: 'import-group-pane') } diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index c5be044a27b..063eef41f77 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -1,20 +1,26 @@ # frozen_string_literal: true module IdeHelper - def ide_data(project:, branch:, path:, merge_request:, fork_info:) - { + # Overridden in EE + def ide_data(project:, fork_info:, params:) + base_data = { 'can-use-new-web-ide' => can_use_new_web_ide?.to_s, 'use-new-web-ide' => use_new_web_ide?.to_s, 'new-web-ide-help-page-path' => help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'), 'user-preferences-path' => profile_preferences_path, - 'branch-name' => branch, - 'file-path' => path, - 'fork-info' => fork_info&.to_json, 'editor-font-src-url' => font_url('jetbrains-mono/JetBrainsMono.woff2'), 'editor-font-family' => 'JetBrains Mono', - 'editor-font-format' => 'woff2', - 'merge-request' => merge_request + 'editor-font-format' => 'woff2' }.merge(use_new_web_ide? ? new_ide_data(project: project) : legacy_ide_data(project: project)) + + return base_data unless project + + base_data.merge( + 'fork-info' => fork_info&.to_json, + 'branch-name' => params[:branch], + 'file-path' => params[:path], + 'merge-request' => params[:merge_request_id] + ) end def can_use_new_web_ide? @@ -76,3 +82,5 @@ module IdeHelper current_user.dismissed_callout?(feature_name: 'web_ide_ci_environments_guidance') end end + +IdeHelper.prepend_mod_with('IdeHelper') diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 46d2d2c42d9..9c68f54f42e 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -156,7 +156,7 @@ module IssuablesHelper end output << content_tag(:strong) do - author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline") + author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline-block") author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-inline d-sm-none") author_output << issuable_meta_author_slot(issuable.author, css_class: 'ml-1') @@ -281,7 +281,9 @@ module IssuablesHelper { hasLinkedAlerts: issue.alert_management_alerts.any?, - canUpdateTimelineEvent: can?(current_user, :admin_incident_management_timeline_event, issue) + canUpdateTimelineEvent: can?(current_user, :admin_incident_management_timeline_event, issue), + currentPath: url_for(safe_params), + currentTab: safe_params[:incident_tab] } end @@ -378,8 +380,10 @@ module IssuablesHelper end def hidden_issuable_icon(issuable) - title = format(_('This %{issuable} is hidden because its author has been banned'), - issuable: issuable.is_a?(Issue) ? _('issue') : _('merge request')) + title = format( + _('This %{issuable} is hidden because its author has been banned'), + issuable: issuable.is_a?(Issue) ? _('issue') : _('merge request') + ) content_tag(:span, class: 'has-tooltip', title: title) do sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom') end diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb index 50e3c3cc5fe..28b30ae051c 100644 --- a/app/helpers/jira_connect_helper.rb +++ b/app/helpers/jira_connect_helper.rb @@ -12,7 +12,7 @@ module JiraConnectHelper 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(installation).to_json : nil, - public_key_storage_enabled: Gitlab.config.jira_connect.enable_public_keys_storage || Gitlab::CurrentSettings.jira_connect_public_key_storage_enabled? + public_key_storage_enabled: Gitlab::CurrentSettings.jira_connect_public_key_storage_enabled? } end diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index 4a5720e757d..bec6cccb977 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -80,9 +80,7 @@ module MarkupHelper ) ) - # since <img> tags are stripped, this can leave empty <a> tags hanging around - # (as our markdown wraps images in links) - strip_empty_link_tags(text).html_safe + render_links(text) end def markdown(text, context = {}) @@ -171,9 +169,22 @@ module MarkupHelper { project: wiki.container } end - def strip_empty_link_tags(text) + # Sanitize and style user references links + # + # @param String text the string to be sanitized + # + # 1. Remove empty <a> tags which are caused by the <img> tags being stripped + # (as our markdown wraps images in links) + # 2. Strip all link tags, except user references, leaving just the link text + # 3. Add a highlight class for current user's references + # + # @return sanitized HTML string + def render_links(text) scrubber = Loofah::Scrubber.new do |node| - node.remove if node.name == 'a' && node.children.empty? + next unless node.name == 'a' + next node.remove if node.children.empty? + next node.replace(node.children) if node['data-reference-type'] != 'user' + next node.append_class('current-user') if current_user && node['data-user'] == current_user.id.to_s end sanitize text, scrubber: scrubber diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index ec395baef9e..7d9be2f93fd 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -2,6 +2,7 @@ module MergeRequestsHelper include Gitlab::Utils::StrongMemoize + include CompareHelper def create_mr_button_from_event?(event) create_mr_button?(from: event.branch_name, source_project: event.project) @@ -185,6 +186,7 @@ module MergeRequestsHelper endpoint_metadata: @endpoint_metadata_url, endpoint_batch: diffs_batch_project_json_merge_request_path(project, merge_request, 'json', params), endpoint_coverage: @coverage_path, + endpoint_diff_for_path: diff_for_path_namespace_project_merge_request_path(format: 'json', id: merge_request.iid, namespace_id: project.namespace.path, project_id: project.path), help_page_path: help_page_path('user/project/merge_requests/reviews/suggestions.md'), current_user_data: @current_user_data, update_current_user_path: @update_current_user_path, @@ -198,7 +200,8 @@ module MergeRequestsHelper default_suggestion_commit_message: default_suggestion_commit_message, source_project_default_url: @merge_request.source_project && default_url_to_repo(@merge_request.source_project), source_project_full_path: @merge_request.source_project&.full_path, - is_forked: @project.forked?.to_s + is_forked: @project.forked?.to_s, + saved_replies_new_path: profile_saved_replies_path } end diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb index 3dfd30f07db..06deaeb5e9e 100644 --- a/app/helpers/mirror_helper.rb +++ b/app/helpers/mirror_helper.rb @@ -13,7 +13,7 @@ module MirrorHelper docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url } html_escape(_('Git LFS objects will be synced if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. Push mirrors will %{strong_open}not%{strong_close} sync LFS objects over SSH.')) % - { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe } + { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe } end end diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index ddd6469a9e4..201007863b2 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -6,19 +6,19 @@ module Nav return unless current_user menu_sections = [] + data = { title: _('Create new...') } - if group&.persisted? - menu_sections.push(group_menu_section(group)) - elsif project&.persisted? + if project&.persisted? menu_sections.push(project_menu_section(project)) + elsif group&.persisted? + menu_sections.push(group_menu_section(group)) end menu_sections.push(general_menu_section) - { - title: _("Create new..."), - menu_sections: menu_sections.select { |x| x.fetch(:menu_items).any? } - } + data[:menu_sections] = menu_sections.select { |x| x.fetch(:menu_items).any? } + + data end private @@ -51,11 +51,7 @@ module Nav menu_items.push(create_epic_menu_item(group)) if can?(current_user, :admin_group_member, group) - menu_items.push( - invite_members_menu_item( - href: group_group_members_path(group) - ) - ) + menu_items.push(invite_members_menu_item(partial: 'groups/invite_members_top_nav_link')) end { @@ -102,11 +98,7 @@ module Nav end if can_admin_project_member?(project) - menu_items.push( - invite_members_menu_item( - href: project_project_members_path(project) - ) - ) + menu_items.push(invite_members_menu_item(partial: 'projects/invite_members_top_nav_link')) end { @@ -157,16 +149,16 @@ module Nav } end - def invite_members_menu_item(href:) + def invite_members_menu_item(partial:) ::Gitlab::Nav::TopNavMenuItem.build( id: 'invite', title: s_('InviteMember|Invite members'), - emoji: 'shaking_hands', - href: href, + icon: 'shaking_hands', + partial: partial, + component: 'invite_members', data: { - track_action: 'click_link_invite_members', - track_label: 'plus_menu_dropdown', - track_property: 'navigation_top' + trigger_source: 'top-nav', + trigger_element: 'text-emoji' } ) end diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index fb11c183aeb..756baabd249 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -64,7 +64,6 @@ module Nav end def build_anonymous_view_model(builder:) - # 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], @@ -83,6 +82,15 @@ module Nav ) end + if explore_nav_link?(:topics) + builder.add_primary_menu_item_with_shortcut( + header: top_nav_localized_headers[:explore], + active: active_nav_link?(page: topics_explore_projects_path, path: 'projects#topic'), + href: topics_explore_projects_path, + **topics_menu_item_attrs + ) + end + if explore_nav_link?(:snippets) builder.add_primary_menu_item_with_shortcut( header: top_nav_localized_headers[:explore], @@ -123,39 +131,54 @@ module Nav builder.add_view(GROUPS_VIEW, container_view_props(namespace: 'groups', current_item: current_item, submenu: groups_submenu)) end + if dashboard_nav_link?(:your_work) + builder.add_primary_menu_item( + id: 'your-work', + header: top_nav_localized_headers[:switch_to], + title: _('Your work'), + href: dashboard_projects_path, + active: active_nav_link?(controller: []), + icon: 'work', + data: { **menu_data_tracking_attrs('your-work') } + ) + end + + if dashboard_nav_link?(:explore) + builder.add_primary_menu_item( + id: 'explore', + header: top_nav_localized_headers[:switch_to], + title: _('Explore'), + href: explore_projects_path, + active: active_nav_link?(controller: ["explore/groups", "explore/snippets"], page: ["/explore/projects", "/explore", "/explore/projects/topics"], path: ["projects#topic"]), + icon: 'compass', + data: { **menu_data_tracking_attrs('explore') } + ) + end + if dashboard_nav_link?(:milestones) - builder.add_primary_menu_item_with_shortcut( - id: 'milestones', - header: top_nav_localized_headers[:explore], + builder.add_shortcut( + id: 'milestones-shortcut', title: _('Milestones'), href: dashboard_milestones_path, - active: active_nav_link?(controller: 'dashboard/milestones'), - icon: 'clock', - data: { **menu_data_tracking_attrs('milestones') }, - shortcut_class: 'dashboard-shortcuts-milestones' + css_class: 'dashboard-shortcuts-milestones' ) end 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') }, + builder.add_shortcut( + id: 'snippets-shortcut', + title: _('Snippets'), href: dashboard_snippets_path, - **snippets_menu_item_attrs + css_class: 'dashboard-shortcuts-snippets' ) end if dashboard_nav_link?(:activity) - builder.add_primary_menu_item_with_shortcut( - id: 'activity', - header: top_nav_localized_headers[:explore], + builder.add_shortcut( + id: 'activity-shortcut', title: _('Activity'), href: activity_dashboard_path, - active: active_nav_link?(path: 'dashboard#activity'), - icon: 'history', - data: { **menu_data_tracking_attrs('activity') }, - shortcut_class: 'dashboard-shortcuts-activity' + css_class: 'dashboard-shortcuts-activity' ) end @@ -220,6 +243,15 @@ module Nav } end + def topics_menu_item_attrs + { + id: 'topics', + title: _('Topics'), + icon: 'labels', + shortcut_class: 'dashboard-shortcuts-topics' + } + end + def snippets_menu_item_attrs { id: 'snippets', diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index d0421cd5184..59ffe6a183e 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -9,10 +9,29 @@ module NavHelper header_links.include?(link) end + def page_has_sidebar? + defined?(@left_sidebar) && @left_sidebar + end + + def page_has_collapsed_sidebar? + page_has_sidebar? && collapsed_sidebar? + end + + def page_has_collapsed_super_sidebar? + page_has_sidebar? && collapsed_super_sidebar? + end + def page_with_sidebar_class class_name = page_gutter_class - class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar - class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar + + if show_super_sidebar? + class_name << 'page-with-super-sidebar' if page_has_sidebar? + class_name << 'page-with-super-sidebar-collapsed' if page_has_collapsed_super_sidebar? + else + class_name << 'page-with-contextual-sidebar' if page_has_sidebar? + class_name << 'page-with-icon-sidebar' if page_has_collapsed_sidebar? + end + class_name -= ['right-sidebar-expanded'] if defined?(@right_sidebar) && !@right_sidebar class_name @@ -66,11 +85,21 @@ module NavHelper end def show_super_sidebar? - Feature.enabled?(:super_sidebar_nav, current_user) && current_user&.use_new_navigation + Feature.enabled?(:super_sidebar_nav, current_user) && current_user&.use_new_navigation && super_sidebar_supported? end private + # This is a temporary measure until we support all other existing sidebars: + # https://gitlab.com/gitlab-org/gitlab/-/issues/391500 + # https://gitlab.com/gitlab-org/gitlab/-/issues/391501 + # https://gitlab.com/gitlab-org/gitlab/-/issues/391502 + def super_sidebar_supported? + return true if @nav.nil? + + %w(your_work explore project group profile user_profile).include?(@nav) + end + def get_header_links links = if current_user [:user_dropdown] diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index b47f4633348..3e8872dc199 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -77,8 +77,10 @@ module NotesHelper line_type: line_type } - button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', - data: data, title: 'Add a reply' + button_tag 'Reply...', + class: 'btn btn-text-field js-discussion-reply-button', + data: data, + title: 'Add a reply' end def note_max_access_for_user(note) @@ -151,7 +153,6 @@ module NotesHelper def initial_notes_data(autocomplete) { notesUrl: notes_url, - notesIds: @noteable.notes.pluck(:id), # rubocop: disable CodeReuse/ActiveRecord now: Time.now.to_i, diffView: diff_view, enableGFM: { diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb index baeb9a477c3..8528f5f04f7 100644 --- a/app/helpers/operations_helper.rb +++ b/app/helpers/operations_helper.rb @@ -12,7 +12,7 @@ module OperationsHelper def alerts_settings_data(disabled: false) setting = project_incident_management_setting - templates = setting.available_issue_templates.map { |t| { key: t.key, name: t.name } } + templates = setting.available_issue_templates.map { |t| { value: t.key, text: t.name } } { 'prometheus_activated' => prometheus_integration.manual_configuration?.to_s, diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index f9ec20bdd01..dec1943db54 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -27,9 +27,14 @@ module PackagesHelper presenter.detail_view.to_json end - def pypi_registry_url(project_id) - full_url = expose_url(api_v4_projects_packages_pypi_simple_package_name_path({ id: project_id, package_name: '' }, true)) - full_url.sub!('://', '://__token__:<your_personal_token>@') + def pypi_registry_url(project) + full_url = expose_url(api_v4_projects_packages_pypi_simple_package_name_path({ id: project.id, package_name: '' }, true)) + + if project.project_feature.public_packages? + full_url + else + full_url.sub!('://', '://__token__:<your_personal_token>@') + end end def composer_registry_url(group_id) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 4a218984af1..9bcabd7d9c6 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -175,7 +175,7 @@ module PageLayoutHelper current_emoji: user.status.emoji.to_s, current_message: user.status.message.to_s, current_availability: user.status.availability.to_s, - current_clear_status_after: user.status.clear_status_at&.to_s(:iso8601) + current_clear_status_after: user_clear_status_at(user) }) end diff --git a/app/helpers/plan_limits_helper.rb b/app/helpers/plan_limits_helper.rb new file mode 100644 index 00000000000..71869b3ba30 --- /dev/null +++ b/app/helpers/plan_limits_helper.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module PlanLimitsHelper + def plan_limit_setting_description(limit_name) + case limit_name + when :ci_pipeline_size + s_('AdminSettings|Maximum number of jobs in a single pipeline') + when :ci_active_jobs + s_('AdminSettings|Total number of jobs in currently active pipelines') + when :ci_active_pipelines + s_('AdminSettings|Maximum number of active pipelines per project') + when :ci_project_subscriptions + s_('AdminSettings|Maximum number of pipeline subscriptions to and from a project') + when :ci_pipeline_schedules + s_('AdminSettings|Maximum number of pipeline schedules') + when :ci_needs_size_limit + s_('AdminSettings|Maximum number of DAG dependencies that a job can have') + when :ci_registered_group_runners + s_('AdminSettings|Maximum number of runners registered per group') + when :ci_registered_project_runners + s_('AdminSettings|Maximum number of runners registered per project') + when :pipeline_hierarchy_size + s_("AdminSettings|Maximum number of downstream pipelines in a pipeline's hierarchy tree") + else + raise ArgumentError, "No description available for plan limit #{limit_name}" + end + end +end diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb index 471565d162c..fc4ad10db21 100644 --- a/app/helpers/projects/error_tracking_helper.rb +++ b/app/helpers/projects/error_tracking_helper.rb @@ -5,8 +5,7 @@ module Projects::ErrorTrackingHelper error_tracking_enabled = !!project.error_tracking_setting&.enabled? { - 'index-path' => project_error_tracking_index_path(project, - format: :json), + 'index-path' => project_error_tracking_index_path(project, format: :json), 'user-can-enable-error-tracking' => can?(current_user, :admin_operations, project).to_s, 'enable-error-tracking-link' => project_settings_operations_path(project), 'error-tracking-enabled' => error_tracking_enabled.to_s, diff --git a/app/helpers/projects/settings/branch_rules_helper.rb b/app/helpers/projects/settings/branch_rules_helper.rb new file mode 100644 index 00000000000..e53275d8183 --- /dev/null +++ b/app/helpers/projects/settings/branch_rules_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Projects + module Settings + module BranchRulesHelper + def branch_rules_data(project) + { + project_path: project.full_path, + protected_branches_path: project_settings_repository_path(project, anchor: 'js-protected-branches-settings'), + approval_rules_path: project_settings_merge_requests_path(project, + anchor: 'js-merge-request-approval-settings'), + status_checks_path: project_settings_merge_requests_path(project, anchor: 'js-merge-request-settings'), + branches_path: project_branches_path(project), + show_status_checks: 'false', + show_approvers: 'false', + show_code_owners: 'false' + } + end + end + end +end + +Projects::Settings::BranchRulesHelper.prepend_mod diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 04190bc442b..a854b9990d2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -133,6 +133,7 @@ module ProjectsHelper { source_name: source_project.full_name, source_path: project_path(source_project), + source_default_branch: source_default_branch, ahead_compare_path: project_compare_path( project, from: source_default_branch, to: ref, from_project_id: source_project.id ), @@ -474,7 +475,7 @@ 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 February 2023. 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}. Contact GitLab Support if you have any additional questions.') else 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 @@ -498,6 +499,18 @@ module ProjectsHelper format_cached_count(1000, number) end + def remote_mirror_setting_enabled? + false + end + + def http_clone_url_to_repo(project) + project.http_url_to_repo + end + + def ssh_clone_url_to_repo(project) + project.ssh_url_to_repo + end + private def localized_access_names @@ -753,7 +766,7 @@ module ProjectsHelper end def show_visibility_confirm_modal?(project) - project.unlink_forks_upon_visibility_decrease_enabled? && project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && project.forks_count > 0 + project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && project.forks_count > 0 end def confirm_reduce_visibility_message(project) diff --git a/app/helpers/registrations_helper.rb b/app/helpers/registrations_helper.rb index 1724e11a6f1..fcd560dbe8c 100644 --- a/app/helpers/registrations_helper.rb +++ b/app/helpers/registrations_helper.rb @@ -11,8 +11,8 @@ module RegistrationsHelper } end - def arkose_labs_challenge_enabled? - false + def signup_box_template + 'devise/shared/signup_box' end end diff --git a/app/helpers/routing/projects_helper.rb b/app/helpers/routing/projects_helper.rb index f4732e398f0..a0073f9c5ba 100644 --- a/app/helpers/routing/projects_helper.rb +++ b/app/helpers/routing/projects_helper.rb @@ -43,9 +43,10 @@ module Routing end def work_item_url(entity, *args) - unless Feature.enabled?(:use_iid_in_work_items_path, entity.project.group) - return project_work_items_url(entity.project, entity.id, *args) - end + # TODO: we do not have a route to access group level work items yet. + # That is to be done as part of view group level work item issue: + # see https://gitlab.com/gitlab-org/gitlab/-/work_items/393987?iid_path=true + return unless entity.project.present? options = args.first || {} options[:iid_path] = true diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index ca5436ff019..d62dc038388 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -204,7 +204,9 @@ module SearchHelper if search_has_project? hash[:project] = { id: @project.id, name: @project.name } - hash[:project_metadata] = { issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project) } + hash[:project_metadata] = { mr_path: project_merge_requests_path(@project) } + hash[:project_metadata][:issues_path] = project_issues_path(@project) if @project.feature_available?(:issues, current_user) + hash[:code_search] = search_scope.nil? hash[:ref] = @ref if @ref && can?(current_user, :read_code, @project) end diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 27020738515..6c9688b0f9d 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -23,38 +23,100 @@ module SidebarsHelper end end - def project_sidebar_context(project, user, current_ref, ref_type: nil) + def project_sidebar_context(project, user, current_ref, ref_type: nil, **args) context_data = project_sidebar_context_data(project, user, current_ref, ref_type: ref_type) - Sidebars::Projects::Context.new(**context_data) + Sidebars::Projects::Context.new(**context_data, **args) end - def group_sidebar_context(group, user) + def group_sidebar_context(group, user, **args) context_data = group_sidebar_context_data(group, user) - Sidebars::Groups::Context.new(**context_data) + Sidebars::Groups::Context.new(**context_data, **args) end - def super_sidebar_context(user, group:, project:) + def your_work_sidebar_context(user, **args) + context_data = your_work_context_data(user) + + Sidebars::Context.new(**context_data, **args) + end + + def super_sidebar_context(user, group:, project:, panel:) { + current_menu_items: panel.super_sidebar_menu_items, + current_context_header: panel.super_sidebar_context_header, name: user.name, username: user.username, avatar_url: user.avatar_url, + has_link_to_profile: current_user_menu?(:profile), + link_to_profile: user_url(user), + logo_url: current_appearance&.header_logo_path, + status: { + can_update: can?(current_user, :update_user_status, current_user), + busy: user.status&.busy?, + customized: user.status&.customized?, + availability: user.status&.availability.to_s, + emoji: user.status&.emoji, + message: user.status&.message_html&.html_safe, + clear_after: user_clear_status_at(user) + }, + trial: { + has_start_trial: current_user_menu?(:start_trial), + url: trials_link_url + }, + settings: { + has_settings: current_user_menu?(:settings), + profile_path: profile_path, + profile_preferences_path: profile_preferences_path + }, + can_sign_out: current_user_menu?(:sign_out), + sign_out_link: destroy_user_session_path, assigned_open_issues_count: user.assigned_open_issues_count, todos_pending_count: user.todos_pending_count, issues_dashboard_path: issues_dashboard_path(assignee_username: user.username), total_merge_requests_count: user_merge_requests_counts[:total], create_new_menu_groups: create_new_menu_groups(group: group, project: project), merge_request_menu: create_merge_request_menu(user), + projects_path: projects_path, + groups_path: groups_path, support_path: support_url, display_whats_new: display_whats_new?, whats_new_most_recent_release_items_count: whats_new_most_recent_release_items_count, whats_new_version_digest: whats_new_version_digest, show_version_check: show_version_check?, gitlab_version: Gitlab.version_info, - gitlab_version_check: gitlab_version_check + gitlab_version_check: gitlab_version_check, + gitlab_com_but_not_canary: Gitlab.com_but_not_canary?, + gitlab_com_and_canary: Gitlab.com_and_canary?, + canary_toggle_com_url: Gitlab::Saas.canary_toggle_com_url, + current_context: super_sidebar_current_context(project: project, group: group) } end + def super_sidebar_nav_panel( + nav: nil, project: nil, user: nil, group: nil, current_ref: nil, ref_type: nil, + viewed_user: nil) + context_adds = { route_is_active: method(:active_nav_link?), is_super_sidebar: true } + case nav + when 'project' + context = project_sidebar_context(project, user, current_ref, ref_type: ref_type, **context_adds) + Sidebars::Projects::SuperSidebarPanel.new(context) + when 'group' + context = group_sidebar_context(group, user, **context_adds) + Sidebars::Groups::SuperSidebarPanel.new(context) + when 'profile' + context = Sidebars::Context.new(current_user: user, container: user, **context_adds) + Sidebars::UserSettings::Panel.new(context) + when 'user_profile' + context = Sidebars::Context.new(current_user: user, container: viewed_user, **context_adds) + Sidebars::UserProfile::Panel.new(context) + when 'explore' + Sidebars::Explore::Panel.new(Sidebars::Context.new(current_user: user, container: nil, **context_adds)) + else + context = your_work_sidebar_context(user, **context_adds) + Sidebars::YourWork::Panel.new(context) + end + end + private def create_new_menu_groups(group:, project:) @@ -160,6 +222,44 @@ module SidebarsHelper container: group } end + + def your_work_context_data(user) + { + current_user: user, + container: user, + show_security_dashboard: false + } + end + + def super_sidebar_current_context(project: nil, group: nil) + if project&.persisted? + return { + namespace: 'projects', + item: { + id: project.id, + name: project.name, + namespace: project.full_name, + webUrl: project_path(project), + avatarUrl: project.avatar_url + } + } + end + + if group&.persisted? + return { + namespace: 'groups', + item: { + id: group.id, + name: group.name, + namespace: group.full_name, + webUrl: group_path(group), + avatarUrl: group.avatar_url + } + } + end + + {} + end end SidebarsHelper.prepend_mod_with('SidebarsHelper') diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 8558c664977..2f9117a74be 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -45,30 +45,35 @@ module SnippetsHelper def embedded_raw_snippet_button(snippet, blob) return if blob.empty? || blob.binary? || blob.stored_externally? - link_to(external_snippet_icon('doc-code'), - gitlab_raw_snippet_blob_url(snippet, blob.path), - class: 'gl-button btn btn-default', - target: '_blank', - rel: 'noopener noreferrer', - title: 'Open raw') + link_to( + external_snippet_icon('doc-code'), + gitlab_raw_snippet_blob_url(snippet, blob.path), + class: 'gl-button btn btn-default', + target: '_blank', + rel: 'noopener noreferrer', + title: 'Open raw' + ) end def embedded_snippet_download_button(snippet, blob) - link_to(external_snippet_icon('download'), - gitlab_raw_snippet_blob_url(snippet, blob.path, nil, inline: false), - class: 'gl-button btn btn-default', - target: '_blank', - title: 'Download', - rel: 'noopener noreferrer') + link_to( + external_snippet_icon('download'), + gitlab_raw_snippet_blob_url(snippet, blob.path, nil, inline: false), + class: 'gl-button btn btn-default', + target: '_blank', + title: 'Download', + rel: 'noopener noreferrer' + ) end def embedded_copy_snippet_button(blob) return unless blob.rendered_as_text?(ignore_errors: false) - content_tag(:button, - class: 'gl-button btn btn-default copy-to-clipboard-btn', - title: 'Copy snippet contents', - onclick: "copyToClipboard('.blob-content[data-blob-id=\"#{blob.id}\"] > pre')" + content_tag( + :button, + class: 'gl-button btn btn-default copy-to-clipboard-btn', + title: 'Copy snippet contents', + onclick: "copyToClipboard('.blob-content[data-blob-id=\"#{blob.id}\"] > pre')" ) do external_snippet_icon('copy-to-clipboard') end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 4a9596a1347..9038d972f65 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -227,7 +227,7 @@ module SortingHelper options.concat([due_date_option]) if viewing_issues options.concat([popularity_option, label_priority_option]) - options.concat([merged_option]) if viewing_merge_requests + options.concat([merged_option]) if can_sort_by_merged_date?(viewing_merge_requests) options.concat([relative_position_option]) if viewing_issues options.concat([title_option]) @@ -237,6 +237,10 @@ module SortingHelper false end + def can_sort_by_merged_date?(viewing_merge_requests) + viewing_merge_requests && %w[all merged].include?(params[:state]) + end + def due_date_option { value: sort_value_due_date, text: sort_title_due_date, href: page_filter_path(sort: sort_value_due_date) } end diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 3e5f63796b2..a1b6e896475 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -42,8 +42,6 @@ module SystemNoteHelper 'severity' => 'information-o', 'cloned' => 'documents', 'issue_type' => 'pencil', - 'attention_requested' => 'user', - 'attention_request_removed' => 'user', 'contact' => 'users', 'timeline_event' => 'clock', 'relate_to_child' => 'link', diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 4a9dd30a5a2..9b0810f3d17 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -232,13 +232,15 @@ module TodosHelper '' end + due_date = + if is_due_today + _("today") + else + l(todo.target.due_date, format: Date::DATE_FORMATS[:medium]) + end + content = content_tag(:span, class: css_class) do - format(s_("Todos|Due %{due_date}"), due_date: if is_due_today - _("today") - else - l(todo.target.due_date, - format: Date::DATE_FORMATS[:medium]) - end) + format(s_("Todos|Due %{due_date}"), due_date: due_date) end "#{content} ·".html_safe diff --git a/app/helpers/users/callouts_helper.rb b/app/helpers/users/callouts_helper.rb index 2b8368dd29f..af3ac495164 100644 --- a/app/helpers/users/callouts_helper.rb +++ b/app/helpers/users/callouts_helper.rb @@ -11,9 +11,11 @@ module Users UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout' SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout' MERGE_REQUEST_SETTINGS_MOVED_CALLOUT = 'merge_request_settings_moved_callout' + PAGES_MOVED_CALLOUT = 'pages_moved_callout' REGISTRATION_ENABLED_CALLOUT_ALLOWED_CONTROLLER_PATHS = [/^root/, /^dashboard\S*/, /^admin\S*/].freeze WEB_HOOK_DISABLED = 'web_hook_disabled' ULTIMATE_FEATURE_REMOVAL_BANNER = 'ultimate_feature_removal_banner' + BRANCH_RULES_INFO_CALLOUT = 'branch_rules_info_callout' def show_gke_cluster_integration_callout?(project) active_nav_link?(controller: sidebar_operations_paths) && @@ -59,42 +61,47 @@ module Users !user_dismissed?(SECURITY_NEWSLETTER_CALLOUT) end - def web_hook_disabled_dismissed?(project) - return false unless project - - last_failure = Gitlab::Redis::SharedState.with do |redis| - key = "web_hooks:last_failure:project-#{project.id}" - redis.get(key) - end + def web_hook_disabled_dismissed?(object) + return false unless object.is_a?(::WebHooks::HasWebHooks) - last_failure = DateTime.parse(last_failure) if last_failure - - user_dismissed?(WEB_HOOK_DISABLED, last_failure, project: project) + user_dismissed?(WEB_HOOK_DISABLED, object.last_webhook_failure, object: object) end def show_merge_request_settings_callout?(project) !user_dismissed?(MERGE_REQUEST_SETTINGS_MOVED_CALLOUT) && project.merge_requests_enabled? end + def show_pages_menu_callout? + !user_dismissed?(PAGES_MOVED_CALLOUT) + end + + def show_branch_rules_info? + !user_dismissed?(BRANCH_RULES_INFO_CALLOUT) + end + def ultimate_feature_removal_banner_dismissed?(project) return false unless project - user_dismissed?(ULTIMATE_FEATURE_REMOVAL_BANNER, project: project) + user_dismissed?(ULTIMATE_FEATURE_REMOVAL_BANNER, object: project) end private - def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil, project: nil) + def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil, object: nil) return false unless current_user query = { feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than } - if project - current_user.dismissed_callout_for_project?(project: project, **query) + if object + dismissed_callout?(object, query) else current_user.dismissed_callout?(**query) end end + + def dismissed_callout?(object, query) + current_user.dismissed_callout_for_project?(project: object, **query) + end end end diff --git a/app/helpers/users/group_callouts_helper.rb b/app/helpers/users/group_callouts_helper.rb index 0aa4eb89499..92cf41400e7 100644 --- a/app/helpers/users/group_callouts_helper.rb +++ b/app/helpers/users/group_callouts_helper.rb @@ -17,9 +17,11 @@ module Users def user_dismissed_for_group(feature_name, group, ignore_dismissal_earlier_than = nil) return false unless current_user - current_user.dismissed_callout_for_group?(feature_name: feature_name, - group: group, - ignore_dismissal_earlier_than: ignore_dismissal_earlier_than) + current_user.dismissed_callout_for_group?( + feature_name: feature_name, + group: group, + ignore_dismissal_earlier_than: ignore_dismissal_earlier_than + ) end def just_created? diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 62b9eb2b506..e0cf7aa61ee 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -8,10 +8,15 @@ module UsersHelper } end + def user_clear_status_at(user) + # The user.status can be nil when the user has no status, so we need to protect against that case. + # iso8601 is the official RFC supported format for frontend parsing of date: + # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date + user.status&.clear_status_at&.to_s(:iso8601) + end + def user_link(user) - link_to(user.name, user_path(user), - title: user.email, - class: 'has-tooltip commit-committer-link') + link_to(user.name, user_path(user), title: user.email, class: 'has-tooltip commit-committer-link') end def user_email_help_text(user) @@ -79,9 +84,9 @@ module UsersHelper return unless user.status content_tag :span, - class: 'user-status-emoji has-tooltip', - title: user.status.message_html, - data: { html: true, placement: 'top' } do + class: 'user-status-emoji has-tooltip', + title: user.status.message_html, + data: { html: true, placement: 'top' } do emoji_icon user.status.emoji end end @@ -168,6 +173,19 @@ module UsersHelper user.public_email.present? end + def trials_link_url + 'https://about.gitlab.com/free-trial/' + end + + def user_profile_tabs_app_data(user) + { + followees: user.followees.count, + followers: user.followers.count, + user_calendar_path: user_calendar_path(user, :json), + utc_offset: local_timezone_instance(user.timezone).now.utc_offset + } + end + private def admin_users_paths @@ -211,10 +229,6 @@ module UsersHelper tabs end - def trials_link_url - 'https://about.gitlab.com/free-trial/' - end - def trials_allowed?(user) false end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 5ed341ee5e5..c577e2da1bb 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -44,9 +44,8 @@ module VisibilityLevelHelper Gitlab::CurrentSettings.restricted_visibility_levels || [] end - delegate :default_project_visibility, - :default_group_visibility, - to: :'Gitlab::CurrentSettings.current_application_settings' + delegate :default_project_visibility, :default_group_visibility, + to: :'Gitlab::CurrentSettings.current_application_settings' def disallowed_visibility_level?(form_model, level) return false unless form_model.respond_to?(:visibility_level_allowed?) diff --git a/app/helpers/web_hooks/web_hooks_helper.rb b/app/helpers/web_hooks/web_hooks_helper.rb index 514db6ba8a2..ad792f761f8 100644 --- a/app/helpers/web_hooks/web_hooks_helper.rb +++ b/app/helpers/web_hooks/web_hooks_helper.rb @@ -4,19 +4,31 @@ module WebHooks module WebHooksHelper def show_project_hook_failed_callout?(project:) return false if project_hook_page? + + show_hook_failed_callout?(project) + end + + private + + def show_hook_failed_callout?(object) return false unless current_user - return false unless Ability.allowed?(current_user, :read_web_hooks, project) + + return false unless can_access_web_hooks?(object) # Assumes include of Users::CalloutsHelper - return false if web_hook_disabled_dismissed?(project) + return false if web_hook_disabled_dismissed?(object) - project.fetch_web_hook_failure + object.fetch_web_hook_failure end - private - def project_hook_page? current_controller?('projects/hooks') || current_controller?('projects/hook_logs') end + + def can_access_web_hooks?(object) + Ability.allowed?(current_user, :admin_project, object) + end end end + +WebHooks::WebHooksHelper.prepend_mod diff --git a/app/helpers/work_items_helper.rb b/app/helpers/work_items_helper.rb index efa9a2bd463..bc270380fca 100644 --- a/app/helpers/work_items_helper.rb +++ b/app/helpers/work_items_helper.rb @@ -6,7 +6,8 @@ module WorkItemsHelper full_path: project.full_path, issues_list_path: project_issues_path(project), register_path: new_user_registration_path(redirect_to_referer: 'yes'), - sign_in_path: new_session_path(:user, redirect_to_referer: 'yes') + sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'), + saved_replies_new_path: profile_saved_replies_path } end end |