summaryrefslogtreecommitdiff
path: root/app/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'app/helpers')
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_helper.rb73
-rw-r--r--app/helpers/application_settings_helper.rb98
-rw-r--r--app/helpers/avatars_helper.rb20
-rw-r--r--app/helpers/award_emoji_helper.rb8
-rw-r--r--app/helpers/blame_helper.rb21
-rw-r--r--app/helpers/blob_helper.rb113
-rw-r--r--app/helpers/boards_helper.rb7
-rw-r--r--app/helpers/branches_helper.rb12
-rw-r--r--app/helpers/breadcrumbs_helper.rb25
-rw-r--r--app/helpers/broadcast_messages_helper.rb2
-rw-r--r--app/helpers/builds_helper.rb18
-rw-r--r--app/helpers/button_helper.rb20
-rw-r--r--app/helpers/ci_status_helper.rb53
-rw-r--r--app/helpers/commits_helper.rb70
-rw-r--r--app/helpers/compare_helper.rb3
-rw-r--r--app/helpers/conversational_development_index_helper.rb16
-rw-r--r--app/helpers/defer_script_tag_helper.rb6
-rw-r--r--app/helpers/diff_helper.rb94
-rw-r--r--app/helpers/dropdowns_helper.rb35
-rw-r--r--app/helpers/emails_helper.rb19
-rw-r--r--app/helpers/environment_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb2
-rw-r--r--app/helpers/events_helper.rb41
-rw-r--r--app/helpers/explore_helper.rb2
-rw-r--r--app/helpers/external_wiki_helper.rb2
-rw-r--r--app/helpers/form_helper.rb32
-rw-r--r--app/helpers/gitlab_routing_helper.rb157
-rw-r--r--app/helpers/graph_helper.rb11
-rw-r--r--app/helpers/groups_helper.rb30
-rw-r--r--app/helpers/hooks_helper.rb17
-rw-r--r--app/helpers/icons_helper.rb8
-rw-r--r--app/helpers/import_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb188
-rw-r--r--app/helpers/issues_helper.rb28
-rw-r--r--app/helpers/labels_helper.rb30
-rw-r--r--app/helpers/lazy_image_tag_helper.rb24
-rw-r--r--app/helpers/markup_helper.rb34
-rw-r--r--app/helpers/merge_requests_helper.rb75
-rw-r--r--app/helpers/milestones_helper.rb65
-rw-r--r--app/helpers/milestones_routing_helper.rb17
-rw-r--r--app/helpers/nav_helper.rb42
-rw-r--r--app/helpers/notes_helper.rb104
-rw-r--r--app/helpers/notifications_helper.rb39
-rw-r--r--app/helpers/page_layout_helper.rb4
-rw-r--r--app/helpers/pagination_helper.rb21
-rw-r--r--app/helpers/performance_bar_helper.rb7
-rw-r--r--app/helpers/pipeline_schedules_helper.rb11
-rw-r--r--app/helpers/preferences_helper.rb2
-rw-r--r--app/helpers/profiles_helper.rb7
-rw-r--r--app/helpers/projects_helper.rb216
-rw-r--r--app/helpers/rss_helper.rb2
-rw-r--r--app/helpers/search_helper.rb50
-rw-r--r--app/helpers/selects_helper.rb10
-rw-r--r--app/helpers/snippets_helper.rb13
-rw-r--r--app/helpers/sorting_helper.rb10
-rw-r--r--app/helpers/storage_health_helper.rb37
-rw-r--r--app/helpers/submodule_helper.rb69
-rw-r--r--app/helpers/system_note_helper.rb5
-rw-r--r--app/helpers/tab_helper.rb3
-rw-r--r--app/helpers/tags_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb29
-rw-r--r--app/helpers/tree_helper.rb6
-rw-r--r--app/helpers/triggers_helper.rb2
-rw-r--r--app/helpers/u2f_helper.rb2
-rw-r--r--app/helpers/users_helper.rb10
-rw-r--r--app/helpers/visibility_level_helper.rb10
-rw-r--r--app/helpers/webpack_helper.rb25
-rw-r--r--app/helpers/wiki_helper.rb6
69 files changed, 1565 insertions, 661 deletions
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 16136d02530..cdf5fa5d4b7 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -20,7 +20,7 @@ module AppearancesHelper
end
def brand_item
- @appearance ||= Appearance.first
+ @appearance ||= Appearance.current
end
def brand_header_logo
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index fff57472a4f..bcee81bdc15 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,7 +68,7 @@ module ApplicationHelper
end
end
- def avatar_icon(user_or_email = nil, size = nil, scale = 2)
+ def avatar_icon(user_or_email = nil, size = nil, scale = 2, only_path: true)
user =
if user_or_email.is_a?(User)
user_or_email
@@ -77,7 +77,7 @@ module ApplicationHelper
end
if user
- user.avatar_url(size) || default_avatar
+ user.avatar_url(size: size, only_path: only_path) || default_avatar
else
gravatar_icon(user_or_email, size, scale)
end
@@ -131,10 +131,7 @@ module ApplicationHelper
end
def body_data_page
- path = controller.controller_path.split('/')
- namespace = path.first if path.second
-
- [namespace, controller.controller_name, controller.action_name].compact.join(':')
+ [*controller.controller_path.split('/'), controller.action_name].compact.join(':')
end
# shortcut for gitlab config
@@ -167,9 +164,9 @@ module ApplicationHelper
css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
css_classes << " #{html_class}" unless html_class.blank?
- element = content_tag :time, time.strftime("%b %d, %Y"),
+ element = content_tag :time, l(time, format: "%b %d, %Y"),
class: css_classes,
- title: time.to_time.in_time_zone.to_s(:medium),
+ title: l(time.to_time.in_time_zone, format: :timeago_tooltip),
datetime: time.to_time.getutc.iso8601,
data: {
toggle: 'tooltip',
@@ -180,16 +177,16 @@ module ApplicationHelper
element
end
- def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', include_author: false)
- return if object.updated_at == object.created_at
+ def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', exclude_author: false)
+ return unless object.is_edited?
- content_tag :small, class: "edited-text" do
- output = content_tag(:span, "Edited ")
- output << time_ago_with_tooltip(object.updated_at, placement: placement, html_class: html_class)
+ content_tag :small, class: 'edited-text' do
+ output = content_tag(:span, 'Edited ')
+ output << time_ago_with_tooltip(object.last_edited_at, placement: placement, html_class: html_class)
- if include_author && object.updated_by && object.updated_by != object.author
- output << content_tag(:span, " by ")
- output << link_to_member(object.project, object.updated_by, avatar: false, author_class: nil)
+ 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)
end
output
@@ -204,6 +201,10 @@ module ApplicationHelper
'https://' + promo_host
end
+ def support_url
+ current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
+ end
+
def page_filter_path(options = {})
without = options.delete(:without)
add_label = options.delete(:label)
@@ -263,7 +264,11 @@ module ApplicationHelper
end
def page_class
- "issue-boards-page" if current_controller?(:boards)
+ class_names = []
+ class_names << 'issue-boards-page' if current_controller?(:boards)
+ class_names << 'with-performance-bar' if performance_bar_enabled?
+
+ class_names
end
# Returns active css class when condition returns true
@@ -275,7 +280,37 @@ module ApplicationHelper
'active' if condition
end
- def show_user_callout?
- cookies[:user_callout_dismissed] == 'true'
+ def show_callout?(name)
+ cookies[name] != 'true'
+ end
+
+ def linkedin_url(user)
+ name = user.linkedin
+ if name =~ %r{\Ahttps?:\/\/(www\.)?linkedin\.com\/in\/}
+ name
+ else
+ "https://www.linkedin.com/in/#{name}"
+ end
+ end
+
+ def twitter_url(user)
+ name = user.twitter
+ if name =~ %r{\Ahttps?:\/\/(www\.)?twitter\.com\/}
+ name
+ else
+ "https://www.twitter.com/#{name}"
+ end
+ end
+
+ def show_new_nav?
+ cookies["new_nav"] == "true"
+ end
+
+ def collapsed_sidebar?
+ cookies["sidebar_collapsed"] == "true"
+ end
+
+ def show_new_repo?
+ cookies["new_repo"] == "true" && body_data_page != 'projects:show'
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index ca326dd0627..150188f0b65 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -1,7 +1,8 @@
module ApplicationSettingsHelper
+ extend self
delegate :gravatar_enabled?,
:signup_enabled?,
- :signin_enabled?,
+ :password_authentication_enabled?,
:akismet_enabled?,
:koding_enabled?,
to: :current_application_settings
@@ -34,17 +35,17 @@ module ApplicationSettingsHelper
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
- def restricted_level_checkboxes(help_block_id)
- Gitlab::VisibilityLevel.options.map do |name, level|
+ def restricted_level_checkboxes(help_block_id, checkbox_name)
+ Gitlab::VisibilityLevel.values.map do |level|
checked = restricted_visibility_levels(true).include?(level)
css_class = checked ? 'active' : ''
- checkbox_name = "application_setting[restricted_visibility_levels][]"
+ tag_name = "application_setting_visibility_level_#{level}"
- label_tag(name, class: css_class) do
+ label_tag(tag_name, class: css_class) do
check_box_tag(checkbox_name, level, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id,
- id: name) + visibility_level_icon(level) + name
+ id: tag_name) + visibility_level_icon(level) + visibility_level_label(level)
end
end
end
@@ -91,4 +92,89 @@ module ApplicationSettingsHelper
def sidekiq_queue_options_for_select
options_for_select(Sidekiq::Queue.all.map(&:name), @application_setting.sidekiq_throttling_queues)
end
+
+ def visible_attributes
+ [
+ :admin_notification_email,
+ :after_sign_out_path,
+ :after_sign_up_text,
+ :akismet_api_key,
+ :akismet_enabled,
+ :clientside_sentry_dsn,
+ :clientside_sentry_enabled,
+ :container_registry_token_expire_delay,
+ :default_artifacts_expire_in,
+ :default_branch_protection,
+ :default_group_visibility,
+ :default_project_visibility,
+ :default_projects_limit,
+ :default_snippet_visibility,
+ :disabled_oauth_sign_in_sources,
+ :domain_blacklist_enabled,
+ :domain_blacklist_raw,
+ :domain_whitelist_raw,
+ :email_author_in_body,
+ :enabled_git_access_protocol,
+ :gravatar_enabled,
+ :help_page_hide_commercial_content,
+ :help_page_support_url,
+ :help_page_text,
+ :home_page_url,
+ :housekeeping_bitmaps_enabled,
+ :housekeeping_enabled,
+ :housekeeping_full_repack_period,
+ :housekeeping_gc_period,
+ :housekeeping_incremental_repack_period,
+ :html_emails_enabled,
+ :import_sources,
+ :koding_enabled,
+ :koding_url,
+ :max_artifacts_size,
+ :max_attachment_size,
+ :max_pages_size,
+ :metrics_enabled,
+ :metrics_host,
+ :metrics_method_call_threshold,
+ :metrics_packet_size,
+ :metrics_pool_size,
+ :metrics_port,
+ :metrics_sample_interval,
+ :metrics_timeout,
+ :password_authentication_enabled,
+ :performance_bar_allowed_group_id,
+ :performance_bar_enabled,
+ :plantuml_enabled,
+ :plantuml_url,
+ :polling_interval_multiplier,
+ :project_export_enabled,
+ :prometheus_metrics_enabled,
+ :recaptcha_enabled,
+ :recaptcha_private_key,
+ :recaptcha_site_key,
+ :repository_checks_enabled,
+ :repository_storages,
+ :require_two_factor_authentication,
+ :restricted_visibility_levels,
+ :send_user_confirmation_email,
+ :sentry_dsn,
+ :sentry_enabled,
+ :session_expire_delay,
+ :shared_runners_enabled,
+ :shared_runners_text,
+ :sidekiq_throttling_enabled,
+ :sidekiq_throttling_factor,
+ :sidekiq_throttling_queues,
+ :sign_in_text,
+ :signup_enabled,
+ :terminal_max_session_time,
+ :two_factor_grace_period,
+ :unique_ips_limit_enabled,
+ :unique_ips_limit_per_user,
+ :unique_ips_limit_time_window,
+ :usage_ping_enabled,
+ :user_default_external,
+ :user_oauth_applications,
+ :version_check_enabled
+ ]
+ end
end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index b7e0ff8ecd0..4b51269533c 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -8,18 +8,24 @@ module AvatarsHelper
}))
end
- def user_avatar(options = {})
+ def user_avatar_without_link(options = {})
avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name]
- css_class = options[:css_class] || ''
-
- avatar = image_tag(
- avatar_icon(options[:user] || options[:user_email], avatar_size),
- class: "avatar has-tooltip s#{avatar_size} #{css_class}",
+ avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size)
+ data_attributes = { container: 'body' }
+
+ image_tag(
+ avatar_url,
+ class: %W[avatar has-tooltip s#{avatar_size}].push(*options[:css_class]),
alt: "#{user_name}'s avatar",
title: user_name,
- data: { container: 'body' }
+ data: data_attributes,
+ lazy: true
)
+ end
+
+ def user_avatar(options = {})
+ avatar = user_avatar_without_link(options)
if options[:user]
link_to(avatar, user_path(options[:user]))
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
index 167b09e678f..86b19368cfd 100644
--- a/app/helpers/award_emoji_helper.rb
+++ b/app/helpers/award_emoji_helper.rb
@@ -1,10 +1,14 @@
module AwardEmojiHelper
def toggle_award_url(awardable)
- return url_for([:toggle_award_emoji, awardable]) unless @project
+ return url_for([:toggle_award_emoji, awardable]) unless @project || awardable.is_a?(Note)
if awardable.is_a?(Note)
# We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (4.5x)
- toggle_award_emoji_namespace_project_note_url(@project.namespace, @project, awardable.id)
+ if awardable.for_personal_snippet?
+ toggle_award_emoji_snippet_note_path(awardable.noteable, awardable)
+ else
+ toggle_award_emoji_project_note_path(@project, awardable.id)
+ end
else
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
end
diff --git a/app/helpers/blame_helper.rb b/app/helpers/blame_helper.rb
new file mode 100644
index 00000000000..d1dc4d94560
--- /dev/null
+++ b/app/helpers/blame_helper.rb
@@ -0,0 +1,21 @@
+module BlameHelper
+ def age_map_duration(blame_groups, project)
+ now = Time.zone.now
+ start_date = blame_groups.map { |blame_group| blame_group[:commit].committed_date }
+ .append(project.created_at).min
+
+ {
+ now: now,
+ started_days_ago: (now - start_date).to_i / 1.day
+ }
+ end
+
+ def age_map_class(commit_date, duration)
+ commit_date_days_ago = (duration[:now] - commit_date).to_i / 1.day
+ # Numbers 0 to 10 come from this calculation, but only commits on the oldest
+ # day get number 10 (all other numbers can be multiple days), so the range
+ # is normalized to 0-9
+ age_group = [(10 * commit_date_days_ago) / duration[:started_days_ago], 9].min
+ "blame-commit-age-#{age_group}"
+ end
+end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 36b16421e8f..18075ee8be7 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -9,7 +9,7 @@ module BlobHelper
end
def edit_path(project = @project, ref = @ref, path = @path, options = {})
- namespace_project_edit_blob_path(project.namespace, project,
+ project_edit_blob_path(project,
tree_join(ref, path),
options[:link_opts])
end
@@ -18,7 +18,7 @@ module BlobHelper
blob = options.delete(:blob)
blob ||= project.repository.blob_at(ref, path) rescue nil
- return unless blob
+ return unless blob && blob.readable_text?
common_classes = "btn js-edit-blob #{options[:extra_class]}"
@@ -33,7 +33,7 @@ module BlobHelper
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+ fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
button_tag 'Edit',
class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -52,7 +52,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref)
button_tag label, class: "#{common_classes} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
- elsif blob.lfs_pointer?
+ elsif blob.stored_externally?
button_tag label, class: "#{common_classes} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
@@ -62,7 +62,7 @@ module BlobHelper
notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+ fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
button_tag label,
class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -95,7 +95,7 @@ module BlobHelper
end
def can_modify_blob?(blob, project = @project, ref = @ref)
- !blob.lfs_pointer? && can_edit_tree?(project, ref)
+ !blob.stored_externally? && can_edit_tree?(project, ref)
end
def leave_edit_message
@@ -118,28 +118,25 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw")
end
- def blob_text_viewable?(blob)
- blob && blob.text? && !blob.lfs_pointer? && !blob.only_display_raw?
- end
-
- def blob_rendered_as_text?(blob)
- blob_text_viewable?(blob) && blob.to_partial_path(@project) == 'text'
- end
-
- def blob_size(blob)
- if blob.lfs_pointer?
- blob.lfs_size
- else
- blob.size
+ def blob_raw_path
+ if @build && @entry
+ raw_project_job_artifacts_path(@project, @build, path: @entry.path)
+ elsif @snippet
+ if @snippet.project_id
+ raw_project_snippet_path(@project, @snippet)
+ else
+ raw_snippet_path(@snippet)
+ end
+ elsif @blob
+ project_raw_path(@project, @id)
end
end
# SVGs can contain malicious JavaScript; only include whitelisted
# elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements.
- def sanitize_svg(blob)
- blob.data = Gitlab::Sanitizers::SVG.clean(blob.data)
- blob
+ def sanitize_svg_data(data)
+ Gitlab::Sanitizers::SVG.clean(data)
end
# If we blindly set the 'real' content type when serving a Git blob we
@@ -221,13 +218,75 @@ module BlobHelper
clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
end
- def copy_blob_content_button(blob)
- return if markup?(blob.name)
+ def copy_blob_source_button(blob)
+ return unless blob.rendered_as_text?(ignore_errors: false)
+
+ clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm js-copy-blob-source-btn", title: "Copy source to clipboard")
+ end
+
+ def open_raw_blob_button(blob)
+ return if blob.empty?
+
+ if blob.raw_binary? || blob.stored_externally?
+ icon = icon('download')
+ title = 'Download'
+ else
+ icon = icon('file-code-o')
+ title = 'Open raw'
+ end
+
+ link_to icon, blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
+ end
+
+ def blob_render_error_reason(viewer)
+ case viewer.render_error
+ when :collapsed
+ "it is larger than #{number_to_human_size(viewer.collapse_limit)}"
+ when :too_large
+ "it is larger than #{number_to_human_size(viewer.size_limit)}"
+ when :server_side_but_stored_externally
+ case viewer.blob.external_storage
+ when :lfs
+ 'it is stored in LFS'
+ when :build_artifact
+ 'it is stored as a job artifact'
+ else
+ 'it is stored externally'
+ end
+ end
+ end
+
+ def blob_render_error_options(viewer)
+ error = viewer.render_error
+ options = []
+
+ if error == :collapsed
+ options << link_to('load it anyway', url_for(params.merge(viewer: viewer.type, expanded: true, format: nil)))
+ end
+
+ # If the error is `:server_side_but_stored_externally`, the simple viewer will show the same error,
+ # so don't bother switching.
+ if viewer.rich? && viewer.blob.rendered_as_text? && error != :server_side_but_stored_externally
+ options << link_to('view the source', '#', class: 'js-blob-viewer-switch-btn', data: { viewer: 'simple' })
+ end
+
+ options << link_to('download it', blob_raw_path, target: '_blank', rel: 'noopener noreferrer')
- clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard")
+ options
end
- def open_raw_file_button(path)
- link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' }
+ def contribution_options(project)
+ options = []
+
+ if can?(current_user, :create_issue, project)
+ options << link_to("submit an issue", new_project_issue_path(project))
+ end
+
+ merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
+ if merge_project
+ options << link_to("create a merge request", project_new_merge_request_path(project))
+ end
+
+ options
end
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index f43827da446..8b33c362a9c 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -3,12 +3,13 @@ module BoardsHelper
board = @board || @boards.first
{
- endpoint: namespace_project_boards_path(@project.namespace, @project),
+ endpoint: project_boards_path(@project),
board_id: board.id,
disabled: "#{!can?(current_user, :admin_list, @project)}",
- issue_link_base: namespace_project_issues_path(@project.namespace, @project),
+ issue_link_base: project_issues_path(@project),
root_path: root_path,
- bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project),
+ bulk_update_path: bulk_update_project_issues_path(@project),
+ default_avatar: image_path(default_avatar)
}
end
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index b7a28b1b4a7..686437fc99a 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -1,14 +1,4 @@
module BranchesHelper
- def can_remove_branch?(project, branch_name)
- if ProtectedBranch.protected?(project, branch_name)
- false
- elsif branch_name == project.repository.root_ref
- false
- else
- can?(current_user, :push_code, project)
- end
- end
-
def filter_branches_path(options = {})
exist_opts = {
search: params[:search],
@@ -17,7 +7,7 @@ module BranchesHelper
options = exist_opts.merge(options)
- namespace_project_branches_path(@project.namespace, @project, @id, options)
+ project_branches_path(@project, @id, options)
end
def can_push_branch?(project, branch_name)
diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb
new file mode 100644
index 00000000000..abe8edd6a8c
--- /dev/null
+++ b/app/helpers/breadcrumbs_helper.rb
@@ -0,0 +1,25 @@
+module BreadcrumbsHelper
+ def add_to_breadcrumbs(text, link)
+ @breadcrumbs_extra_links ||= []
+ @breadcrumbs_extra_links.push({
+ text: text,
+ link: link
+ })
+ end
+
+ def breadcrumb_title_link
+ return @breadcrumb_link if @breadcrumb_link
+
+ if controller.available_action?(:index)
+ url_for(action: "index")
+ else
+ request.path
+ end
+ end
+
+ def breadcrumb_title(title)
+ return if defined?(@breadcrumb_title)
+
+ @breadcrumb_title = title
+ end
+end
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index eb03ced67eb..0a15c29cfb5 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -1,5 +1,5 @@
module BroadcastMessagesHelper
- def broadcast_message(message = BroadcastMessage.current)
+ def broadcast_message(message)
return unless message.present?
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index 2fcb7a59fc3..85bc784d53c 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -1,4 +1,16 @@
module BuildsHelper
+ def build_summary(build, skip: false)
+ if build.has_trace?
+ if skip
+ link_to "View job trace", pipeline_job_url(build.pipeline, build)
+ else
+ build.trace.html(last_lines: 10).html_safe
+ end
+ else
+ "No job trace"
+ end
+ end
+
def sidebar_build_class(build, current_build)
build_class = ''
build_class += ' active' if build.id === current_build.id
@@ -8,8 +20,8 @@ module BuildsHelper
def javascript_build_options
{
- page_url: namespace_project_build_url(@project.namespace, @project, @build),
- build_url: namespace_project_build_url(@project.namespace, @project, @build, :json),
+ page_url: project_job_url(@project, @build),
+ build_url: project_job_url(@project, @build, :json),
build_status: @build.status,
build_stage: @build.stage,
log_state: ''
@@ -19,7 +31,7 @@ module BuildsHelper
def build_failed_issue_options
{
title: "Build Failed ##{@build.id}",
- description: namespace_project_build_url(@project.namespace, @project, @build)
+ description: project_job_url(@project, @build)
}
end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index c85e96cf78d..bf9ad95b7c2 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -42,23 +42,33 @@ module ButtonHelper
class: "btn #{css_class}",
data: data,
type: :button,
- title: title
+ title: title,
+ aria: {
+ label: title
+ }
end
def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector'
- klass << ' has-tooltip' if current_user.try(:require_password?)
+ klass << ' has-tooltip' if current_user.try(:require_password_creation?) || current_user.try(:require_personal_access_token_creation_for_git_auth?)
protocol = gitlab_config.protocol.upcase
+ tooltip_title =
+ if current_user.try(:require_password_creation?)
+ _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
+ else
+ _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
+ end
+
content_tag (append_link ? :a : :span), protocol,
class: klass,
- href: (project.http_url_to_repo(current_user) if append_link),
+ href: (project.http_url_to_repo if append_link),
data: {
html: true,
placement: placement,
container: 'body',
- title: "Set a password on your account<br>to pull or push via #{protocol}"
+ title: tooltip_title
}
end
@@ -73,7 +83,7 @@ module ButtonHelper
html: true,
placement: placement,
container: 'body',
- title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
+ title: _('Add an SSH key to your profile to pull or push via SSH.')
}
end
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 32b1e7822af..8022547a6ad 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -8,7 +8,7 @@
module CiStatusHelper
def ci_status_path(pipeline)
project = pipeline.project
- namespace_project_pipeline_path(project.namespace, project, pipeline)
+ project_pipeline_path(project, pipeline)
end
def ci_label_for_status(status)
@@ -16,16 +16,18 @@ module CiStatusHelper
return status.label
end
- case status
- when 'success'
- 'passed'
- when 'success_with_warnings'
- 'passed with warnings'
- when 'manual'
- 'waiting for manual action'
- else
- status
- end
+ label = case status
+ when 'success'
+ 'passed'
+ when 'success_with_warnings'
+ 'passed with warnings'
+ when 'manual'
+ 'waiting for manual action'
+ else
+ status
+ end
+ translation = "CiStatusLabel|#{label}"
+ s_(translation)
end
def ci_text_for_status(status)
@@ -35,13 +37,22 @@ module CiStatusHelper
case status
when 'success'
- 'passed'
+ s_('CiStatusText|passed')
when 'success_with_warnings'
- 'passed'
+ s_('CiStatusText|passed')
when 'manual'
- 'blocked'
+ s_('CiStatusText|blocked')
else
- status
+ # All states are already being translated inside the detailed statuses:
+ # :running => Gitlab::Ci::Status::Running
+ # :skipped => Gitlab::Ci::Status::Skipped
+ # :failed => Gitlab::Ci::Status::Failed
+ # :success => Gitlab::Ci::Status::Success
+ # :canceled => Gitlab::Ci::Status::Canceled
+ # The following states are customized above:
+ # :manual => Gitlab::Ci::Status::Manual
+ status_translation = "CiStatusText|#{status}"
+ s_(status_translation)
end
end
@@ -88,10 +99,7 @@ module CiStatusHelper
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
project = pipeline_status.project
- path = pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- pipeline_status.sha)
+ path = pipelines_project_commit_path(project, pipeline_status.sha)
render_status_with_link(
'commit',
@@ -102,10 +110,7 @@ module CiStatusHelper
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
- path = pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- commit)
+ path = pipelines_project_commit_path(project, commit)
render_status_with_link(
'commit',
@@ -116,7 +121,7 @@ module CiStatusHelper
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
project = pipeline.project
- path = namespace_project_pipeline_path(project.namespace, project, pipeline)
+ path = project_pipeline_path(project, pipeline)
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index cef624430da..72e26b64e60 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -15,16 +15,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer))
end
- def image_diff_class(diff)
- if diff.deleted_file
- "deleted"
- elsif diff.new_file
- "added"
- else
- nil
- end
- end
-
def commit_to_html(commit, ref, project)
render 'projects/commits/commit',
commit: commit,
@@ -40,7 +30,7 @@ module CommitsHelper
crumbs = content_tag(:li) do
link_to(
@project.path,
- namespace_project_commits_path(@project.namespace, @project, @ref)
+ project_commits_path(@project, @ref)
)
end
@@ -52,8 +42,7 @@ module CommitsHelper
# The text is just the individual part, but the link needs all the parts before it
link_to(
part,
- namespace_project_commits_path(
- @project.namespace,
+ project_commits_path(
@project,
tree_join(@ref, parts[0..i].join('/'))
)
@@ -74,12 +63,8 @@ module CommitsHelper
# Returns the sorted alphabetically links to branches, separated by a comma
def commit_branches_links(project, branches)
branches.sort.map do |branch|
- link_to(
- namespace_project_tree_path(project.namespace, project, branch)
- ) do
- content_tag :span, class: 'label label-gray' do
- icon('code-fork') + ' ' + branch
- end
+ link_to(project_ref_path(project, branch), class: "label label-gray ref-name") do
+ icon('code-fork') + " #{branch}"
end
end.join(" ").html_safe
end
@@ -88,39 +73,32 @@ module CommitsHelper
def commit_tags_links(project, tags)
sorted = VersionSorter.rsort(tags)
sorted.map do |tag|
- link_to(
- namespace_project_commits_path(project.namespace, project,
- project.repository.find_tag(tag).name)
- ) do
- content_tag :span, class: 'label label-gray' do
- icon('tag') + ' ' + tag
- end
+ link_to(project_ref_path(project, tag), class: "label label-gray ref-name") do
+ icon('tag') + " #{tag}"
end
end.join(" ").html_safe
end
def link_to_browse_code(project, commit)
+ return unless current_controller?(:commits)
+
if @path.blank?
return link_to(
- "Browse Files",
- namespace_project_tree_path(project.namespace, project, commit),
+ _("Browse Files"),
+ project_tree_path(project, commit),
class: "btn btn-default"
)
- end
-
- return unless current_controller?(:projects, :commits)
-
- if @repo.blob_at(commit.id, @path)
+ elsif @repo.blob_at(commit.id, @path)
return link_to(
- "Browse File",
- namespace_project_blob_path(project.namespace, project,
+ _("Browse File"),
+ project_blob_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
elsif @path.present?
return link_to(
- "Browse Directory",
- namespace_project_tree_path(project.namespace, project,
+ _("Browse Directory"),
+ project_tree_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
@@ -135,6 +113,10 @@ module CommitsHelper
commit_action_link('cherry-pick', commit, continue_to_path, btn_class: btn_class, has_tooltip: has_tooltip)
end
+ def commit_signature_badge_classes(additional_classes)
+ %w(btn status-box gpg-status-box) + Array(additional_classes)
+ end
+
protected
# Private: Returns a link to a person. If the person has a matching user and
@@ -146,10 +128,10 @@ module CommitsHelper
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
- user = commit.send(options[:source])
+ user = commit.public_send(options[:source]) # rubocop:disable GitlabSecurity/PublicSend
- source_name = clean(commit.send "#{options[:source]}_name".to_sym)
- source_email = clean(commit.send "#{options[:source]}_email".to_sym)
+ source_name = clean(commit.public_send(:"#{options[:source]}_name")) # rubocop:disable GitlabSecurity/PublicSend
+ source_email = clean(commit.public_send(:"#{options[:source]}_email")) # rubocop:disable GitlabSecurity/PublicSend
person_name = user.try(:name) || source_name
@@ -186,7 +168,7 @@ module CommitsHelper
notice: "#{edit_in_new_fork_notice} Try to #{action} this commit again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(@project.namespace, @project,
+ fork_path = project_forks_path(@project,
namespace_key: current_user.namespace.id,
continue: continue_params)
@@ -196,12 +178,12 @@ module CommitsHelper
def view_file_button(commit_sha, diff_new_path, project)
link_to(
- namespace_project_blob_path(project.namespace, project,
+ project_blob_path(project,
tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file'
) do
- raw('View file @') + content_tag(:span, commit_sha[0..6],
- class: 'commit-short-id')
+ raw('View file @ ') + content_tag(:span, Commit.truncate_sha(commit_sha),
+ class: 'commit-sha')
end
end
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 2aa0449c46e..2c28dd81c87 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -9,8 +9,7 @@ module CompareHelper
end
def create_mr_path(from = params[:from], to = params[:to], project = @project)
- new_namespace_project_merge_request_path(
- project.namespace,
+ project_new_merge_request_path(
project,
merge_request: {
source_branch: to,
diff --git a/app/helpers/conversational_development_index_helper.rb b/app/helpers/conversational_development_index_helper.rb
new file mode 100644
index 00000000000..1ff54415811
--- /dev/null
+++ b/app/helpers/conversational_development_index_helper.rb
@@ -0,0 +1,16 @@
+module ConversationalDevelopmentIndexHelper
+ def score_level(score)
+ if score < 33.33
+ 'low'
+ elsif score < 66.66
+ 'average'
+ else
+ 'high'
+ end
+ end
+
+ def format_score(score)
+ precision = score < 1 ? 2 : 1
+ number_with_precision(score, precision: precision)
+ end
+end
diff --git a/app/helpers/defer_script_tag_helper.rb b/app/helpers/defer_script_tag_helper.rb
new file mode 100644
index 00000000000..e1567556e5e
--- /dev/null
+++ b/app/helpers/defer_script_tag_helper.rb
@@ -0,0 +1,6 @@
+module DeferScriptTagHelper
+ # Override the default ActionView `javascript_include_tag` helper to support page specific deferred loading
+ def javascript_include_tag(*sources)
+ super(*sources, defer: true)
+ end
+end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index dc144906548..28f591a4e22 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -8,24 +8,24 @@ module DiffHelper
[marked_old_line, marked_new_line]
end
- def expand_all_diffs?
- params[:expand_all_diffs].present?
+ def diffs_expanded?
+ params[:expanded].present?
end
def diff_view
@diff_view ||= begin
diff_views = %w(inline parallel)
- diff_view = cookies[:diff_view]
+ diff_view = params[:view] || cookies[:diff_view]
diff_view = diff_views.first unless diff_views.include?(diff_view)
diff_view.to_sym
end
end
def diff_options
- options = { ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? }
+ options = { ignore_whitespace_change: hide_whitespace?, expanded: diffs_expanded? }
if action_name == 'diff_for_path'
- options[:no_collapse] = true
+ options[:expanded] = true
options[:paths] = params.values_at(:old_path, :new_path)
end
@@ -63,15 +63,15 @@ module DiffHelper
def parallel_diff_discussions(left, right, diff_file)
return unless @grouped_diff_discussions
-
+
discussions_left = discussions_right = nil
- if left && (left.unchanged? || left.removed?)
+ if left && left.discussable? && (left.unchanged? || left.removed?)
line_code = diff_file.line_code(left)
discussions_left = @grouped_diff_discussions[line_code]
end
- if right && right.added?
+ if right && right.discussable? && right.added?
line_code = diff_file.line_code(right)
discussions_right = @grouped_diff_discussions[line_code]
end
@@ -88,40 +88,82 @@ module DiffHelper
end
def submodule_link(blob, ref, repository = @repository)
- tree, commit = submodule_links(blob, ref, repository)
- commit_id = if commit.nil?
+ project_url, tree_url = submodule_links(blob, ref, repository)
+ commit_id = if tree_url.nil?
Commit.truncate_sha(blob.id)
else
- link_to Commit.truncate_sha(blob.id), commit
+ link_to Commit.truncate_sha(blob.id), tree_url
end
[
- content_tag(:span, link_to(truncate(blob.name, length: 40), tree)),
+ content_tag(:span, link_to(truncate(blob.name, length: 40), project_url)),
'@',
- content_tag(:span, commit_id, class: 'monospace'),
+ content_tag(:span, commit_id, class: 'commit-sha')
].join(' ').html_safe
end
- def commit_for_diff(diff_file)
- return diff_file.content_commit if diff_file.content_commit
+ def diff_file_blob_raw_path(diff_file)
+ project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
+ end
- if diff_file.deleted_file
- @base_commit || @commit.parent || @commit
- else
- @commit
- end
+ def diff_file_old_blob_raw_path(diff_file)
+ sha = diff_file.old_content_sha
+ return unless sha
+ project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path))
end
def diff_file_html_data(project, diff_file_path, diff_commit_id)
{
- blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
+ blob_diff_path: project_blob_diff_path(project,
tree_join(diff_commit_id, diff_file_path)),
view: diff_view
}
end
- def editable_diff?(diff)
- !diff.deleted_file && @merge_request && @merge_request.source_project
+ def editable_diff?(diff_file)
+ !diff_file.deleted_file? && @merge_request && @merge_request.source_project
+ end
+
+ def diff_render_error_reason(viewer)
+ case viewer.render_error
+ when :too_large
+ "it is too large"
+ when :server_side_but_stored_externally
+ case viewer.diff_file.external_storage
+ when :lfs
+ 'it is stored in LFS'
+ else
+ 'it is stored externally'
+ end
+ end
+ end
+
+ def diff_render_error_options(viewer)
+ diff_file = viewer.diff_file
+ options = []
+
+ blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
+ options << link_to('view the blob', blob_url)
+
+ options
+ end
+
+ def diff_file_changed_icon(diff_file)
+ if diff_file.deleted_file? || diff_file.renamed_file?
+ "minus"
+ elsif diff_file.new_file?
+ "plus"
+ else
+ "adjust"
+ end
+ end
+
+ def diff_file_changed_icon_color(diff_file)
+ if diff_file.deleted_file?
+ "cred"
+ elsif diff_file.new_file?
+ "cgreen"
+ end
end
private
@@ -139,17 +181,17 @@ module DiffHelper
end
def commit_diff_whitespace_link(project, commit, options)
- url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace)
+ url = project_commit_path(project, commit.id, params_with_whitespace)
toggle_whitespace_link(url, options)
end
def diff_merge_request_whitespace_link(project, merge_request, options)
- url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace)
+ url = diffs_project_merge_request_path(project, merge_request, params_with_whitespace)
toggle_whitespace_link(url, options)
end
def diff_compare_whitespace_link(project, from, to, options)
- url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace)
+ url = project_compare_path(project, from, to, params_with_whitespace)
toggle_whitespace_link(url, options)
end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 8ed99642c7a..ff305fa39b4 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -1,27 +1,27 @@
module DropdownsHelper
def dropdown_tag(toggle_text, options: {}, &block)
- content_tag :div, class: "dropdown #{options[:wrapper_class] if options.has_key?(:wrapper_class)}" do
+ content_tag :div, class: "dropdown #{options[:wrapper_class] if options.key?(:wrapper_class)}" do
data_attr = { toggle: "dropdown" }
- if options.has_key?(:data)
+ if options.key?(:data)
data_attr = options[:data].merge(data_attr)
end
dropdown_output = dropdown_toggle(toggle_text, data_attr, options)
- dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.has_key?(:dropdown_class)}") do
+ dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}") do
output = ""
- if options.has_key?(:title)
+ if options.key?(:title)
output << dropdown_title(options[:title])
end
- if options.has_key?(:filter)
+ if options.key?(:filter)
output << dropdown_filter(options[:placeholder])
end
- output << content_tag(:div, class: "dropdown-content #{options[:content_class] if options.has_key?(:content_class)}") do
- capture(&block) if block && !options.has_key?(:footer_content)
+ output << content_tag(:div, class: "dropdown-content #{options[:content_class] if options.key?(:content_class)}") do
+ capture(&block) if block && !options.key?(:footer_content)
end
if block && options[:footer_content]
@@ -41,18 +41,18 @@ module DropdownsHelper
def dropdown_toggle(toggle_text, data_attr, options = {})
default_label = data_attr[:default_label]
- content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
+ content_tag(:button, 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 << icon('chevron-down')
output.html_safe
end
end
- def dropdown_title(title, back: false)
+ def dropdown_title(title, options: {})
content_tag :div, class: "dropdown-title" do
title_output = ""
- if back
+ if options.fetch(:back, false)
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back", aria: { label: "Go back" }, type: "button") do
icon('arrow-left')
end
@@ -60,14 +60,25 @@ module DropdownsHelper
title_output << content_tag(:span, title)
- title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
- icon('times', class: 'dropdown-menu-close-icon')
+ if options.fetch(:close, true)
+ title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-close", aria: { label: "Close" }, type: "button") do
+ icon('times', class: 'dropdown-menu-close-icon')
+ end
end
title_output.html_safe
end
end
+ def dropdown_input(placeholder, input_id: nil)
+ content_tag :div, class: "dropdown-input" do
+ filter_output = text_field_tag input_id, nil, class: "dropdown-input-field dropdown-no-filter", placeholder: placeholder, autocomplete: 'off'
+ filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button")
+
+ filter_output.html_safe
+ end
+ end
+
def dropdown_filter(placeholder, search_id: nil)
content_tag :div, class: "dropdown-input" do
filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off'
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index f927cfc998f..5f11fe62030 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -12,7 +12,7 @@ module EmailsHelper
"action" => {
"@type" => "ViewAction",
"name" => name,
- "url" => url,
+ "url" => url
}
}
@@ -61,9 +61,22 @@ module EmailsHelper
else
image_tag(
image_url('mailers/gitlab_header_logo.gif'),
- size: "55x50",
- alt: "GitLab"
+ size: '55x50',
+ alt: 'GitLab'
)
end
end
+
+ def email_default_heading(text)
+ content_tag :h1, text, style: [
+ "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif",
+ 'color:#333333',
+ 'font-size:18px',
+ 'font-weight:400',
+ 'line-height:1.4',
+ 'padding:0',
+ 'margin:0',
+ 'text-align:center'
+ ].join(';')
+ end
end
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index ff8550439d0..1e78a189c08 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -8,7 +8,7 @@ module EnvironmentHelper
def environment_link_for_build(project, build)
environment = environment_for_build(project, build)
if environment
- link_to environment.name, namespace_project_environment_path(project.namespace, project, environment)
+ link_to environment.name, project_environment_path(project, environment)
else
content_tag :span, build.expanded_environment_name
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 515e802e01e..4ce89f89fa9 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -1,7 +1,7 @@
module EnvironmentsHelper
def environments_list_data
{
- endpoint: namespace_project_environments_path(@project.namespace, @project, format: :json)
+ endpoint: project_environments_path(@project, format: :json)
}
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 5f5c76d3722..48c87dca217 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -10,11 +10,12 @@ module EventsHelper
'deleted' => 'icon_trash_o'
}.freeze
- def link_to_author(event)
+ def link_to_author(event, self_added: false)
author = event.author
if author
- link_to author.name, user_path(author.username), title: author.name
+ name = self_added ? 'You' : author.name
+ link_to name, user_path(author.username), title: name
else
event.author_name
end
@@ -40,7 +41,7 @@ module EventsHelper
link_opts = {
class: "event-filter-link",
id: "#{key}_event_filter",
- title: "Filter by #{tooltip.downcase}",
+ title: "Filter by #{tooltip.downcase}"
}
content_tag :li, class: active do
@@ -98,13 +99,12 @@ module EventsHelper
def event_feed_url(event)
if event.issue?
- namespace_project_issue_url(event.project.namespace, event.project,
+ project_issue_url(event.project,
event.issue)
elsif event.merge_request?
- namespace_project_merge_request_url(event.project.namespace,
- event.project, event.merge_request)
+ project_merge_request_url(event.project, event.merge_request)
elsif event.commit_note?
- namespace_project_commit_url(event.project.namespace, event.project,
+ project_commit_url(event.project,
event.note_target)
elsif event.note?
if event.note_target
@@ -118,15 +118,15 @@ module EventsHelper
def push_event_feed_url(event)
if event.push_with_commits? && event.md_ref?
if event.commits_count > 1
- namespace_project_compare_url(event.project.namespace, event.project,
+ project_compare_url(event.project,
from: event.commit_from, to:
event.commit_to)
else
- namespace_project_commit_url(event.project.namespace, event.project,
+ project_commit_url(event.project,
id: event.commit_to)
end
else
- namespace_project_commits_url(event.project.namespace, event.project,
+ project_commits_url(event.project,
event.ref_name)
end
end
@@ -145,15 +145,9 @@ module EventsHelper
def event_note_target_path(event)
if event.commit_note?
- namespace_project_commit_path(event.project.namespace,
- event.project,
- event.note_target,
- anchor: dom_id(event.target))
+ project_commit_path(event.project, event.note_target, anchor: dom_id(event.target))
elsif event.project_snippet_note?
- namespace_project_snippet_path(event.project.namespace,
- event.project,
- event.note_target,
- anchor: dom_id(event.target))
+ project_snippet_path(event.project, event.note_target, anchor: dom_id(event.target))
else
polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
@@ -163,9 +157,14 @@ module EventsHelper
def event_note_title_html(event)
if event.note_target
- link_to(event_note_target_path(event), title: event.target_title, class: 'has-tooltip') do
- "#{event.note_target_type} #{event.note_target_reference}"
- end
+ text = raw("#{event.note_target_type} ") +
+ if event.commit_note?
+ content_tag(:span, event.note_target_reference, class: 'commit-sha')
+ else
+ event.note_target_reference
+ end
+
+ link_to(text, event_note_target_path(event), title: event.target_title, class: 'has-tooltip')
else
content_tag(:strong, '(deleted)')
end
diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb
index 7bd212a3ef9..b981a1e8242 100644
--- a/app/helpers/explore_helper.rb
+++ b/app/helpers/explore_helper.rb
@@ -10,7 +10,7 @@ module ExploreHelper
personal: params[:personal],
archived: params[:archived],
shared: params[:shared],
- namespace_id: params[:namespace_id],
+ namespace_id: params[:namespace_id]
}
options = exist_opts.merge(options).delete_if { |key, value| value.blank? }
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index defd87d6bbe..8cf890b74a8 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -4,7 +4,7 @@ module ExternalWikiHelper
if external_wiki_service
external_wiki_service.properties['external_wiki_url']
else
- namespace_project_wiki_path(project.namespace, project, :home)
+ project_wiki_path(project, :home)
end
end
end
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 1182939f656..9247b1f72de 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -8,11 +8,35 @@ module FormHelper
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
content_tag(:h4, headline) <<
content_tag(:ul) do
- model.errors.full_messages.
- map { |msg| content_tag(:li, msg) }.
- join.
- html_safe
+ model.errors.full_messages
+ .map { |msg| content_tag(:li, msg) }
+ .join
+ .html_safe
end
end
end
+
+ def issue_assignees_dropdown_options
+ {
+ toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
+ title: 'Select assignee',
+ filter: true,
+ dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee',
+ placeholder: 'Search users',
+ data: {
+ first_user: current_user&.username,
+ null_user: true,
+ current_user: true,
+ project_id: @project.id,
+ field_name: 'issue[assignee_ids][]',
+ default_label: 'Unassigned',
+ 'max-select': 1,
+ 'dropdown-header': 'Assignee',
+ multi_select: true,
+ 'input-meta': 'name',
+ 'always-show-selectbox': true,
+ current_user_info: current_user.to_json(only: [:id, :name])
+ }
+ }
+ end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index e9b7cbbad6a..d4a91e533c1 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -1,132 +1,89 @@
-# Shorter routing method for project and project items
-# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
-# so we use nested routing for project resources which include project and
-# project namespace. To avoid writing long methods every time we define shortcuts for
-# some of routing.
-#
-# For example instead of this:
-#
-# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
-#
-# We can simply use shortcut:
-#
-# merge_request_path(merge_request)
-#
+# Shorter routing method for some project items
module GitlabRoutingHelper
- # Project
- def project_path(project, *args)
- namespace_project_path(project.namespace, project, *args)
- end
-
- def project_url(project, *args)
- namespace_project_url(project.namespace, project, *args)
- end
+ extend ActiveSupport::Concern
- def edit_project_path(project, *args)
- edit_namespace_project_path(project.namespace, project, *args)
+ included do
+ Gitlab::Routing.includes_helpers(self)
end
- def edit_project_url(project, *args)
- edit_namespace_project_url(project.namespace, project, *args)
- end
-
- def project_files_path(project, *args)
- namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref)
- end
-
- def project_commits_path(project, *args)
- namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
- end
-
- def project_pipelines_path(project, *args)
- namespace_project_pipelines_path(project.namespace, project, *args)
- end
-
- def project_environments_path(project, *args)
- namespace_project_environments_path(project.namespace, project, *args)
- end
-
- def project_cycle_analytics_path(project, *args)
- namespace_project_cycle_analytics_path(project.namespace, project, *args)
- end
-
- def project_builds_path(project, *args)
- namespace_project_builds_path(project.namespace, project, *args)
+ # Project
+ def project_tree_path(project, ref = nil, *args)
+ namespace_project_tree_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
end
- def project_container_registry_path(project, *args)
- namespace_project_container_registry_index_path(project.namespace, project, *args)
+ def project_commits_path(project, ref = nil, *args)
+ namespace_project_commits_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
end
- def activity_project_path(project, *args)
- activity_namespace_project_path(project.namespace, project, *args)
+ def project_ref_path(project, ref_name, *args)
+ project_commits_path(project, ref_name, *args)
end
def runners_path(project, *args)
- namespace_project_runners_path(project.namespace, project, *args)
+ project_runners_path(project, *args)
end
def runner_path(runner, *args)
- namespace_project_runner_path(@project.namespace, @project, runner, *args)
+ project_runner_path(@project, runner, *args)
end
def environment_path(environment, *args)
- namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ project_environment_path(environment.project, environment, *args)
end
def environment_metrics_path(environment, *args)
- metrics_namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ metrics_project_environment_path(environment.project, environment, *args)
end
def issue_path(entity, *args)
- namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
+ project_issue_path(entity.project, entity, *args)
end
def merge_request_path(entity, *args)
- namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
+ project_merge_request_path(entity.project, entity, *args)
end
def pipeline_path(pipeline, *args)
- namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
- end
-
- def milestone_path(entity, *args)
- namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
+ project_pipeline_path(pipeline.project, pipeline.id, *args)
end
def issue_url(entity, *args)
- namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
+ project_issue_url(entity.project, entity, *args)
end
def merge_request_url(entity, *args)
- namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
+ project_merge_request_url(entity.project, entity, *args)
end
def pipeline_url(pipeline, *args)
- namespace_project_pipeline_url(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
+ project_pipeline_url(pipeline.project, pipeline.id, *args)
end
- def pipeline_build_url(pipeline, build, *args)
- namespace_project_build_url(pipeline.project.namespace, pipeline.project, build.id, *args)
+ def pipeline_job_url(pipeline, build, *args)
+ project_job_url(pipeline.project, build.id, *args)
end
def commits_url(entity, *args)
- namespace_project_commits_url(entity.project.namespace, entity.project, entity.ref, *args)
+ project_commits_url(entity.project, entity.ref, *args)
end
def commit_url(entity, *args)
- namespace_project_commit_url(entity.project.namespace, entity.project, entity.sha, *args)
+ project_commit_url(entity.project, entity.sha, *args)
end
- def project_snippet_url(entity, *args)
- namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
+ def preview_markdown_path(project, *args)
+ if @snippet.is_a?(PersonalSnippet)
+ preview_markdown_snippets_path
+ else
+ preview_markdown_project_path(project, *args)
+ end
end
def toggle_subscription_path(entity, *args)
if entity.is_a?(Issue)
- toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity)
+ toggle_subscription_project_issue_path(entity.project, entity)
else
- toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity)
+ toggle_subscription_project_merge_request_path(entity.project, entity)
end
end
@@ -140,32 +97,27 @@ module GitlabRoutingHelper
## Members
def project_members_url(project, *args)
- namespace_project_project_members_url(project.namespace, project)
+ project_project_members_url(project, *args)
end
def project_member_path(project_member, *args)
- namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+ project_project_member_path(project_member.source, project_member)
end
def request_access_project_members_path(project, *args)
- request_access_namespace_project_project_members_path(project.namespace, project)
+ request_access_project_project_members_path(project)
end
def leave_project_members_path(project, *args)
- leave_namespace_project_project_members_path(project.namespace, project)
+ leave_project_project_members_path(project)
end
def approve_access_request_project_member_path(project_member, *args)
- approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+ approve_access_request_project_project_member_path(project_member.source, project_member)
end
def resend_invite_project_member_path(project_member, *args)
- resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
- end
-
- # Snippets
- def personal_snippet_url(snippet, *args)
- snippet_url(snippet)
+ resend_invite_project_project_member_path(project_member.source, project_member)
end
# Groups
@@ -199,28 +151,37 @@ module GitlabRoutingHelper
def artifacts_action_path(path, project, build)
action, path_params = path.split('/', 2)
- args = [project.namespace, project, build, path_params]
+ args = [project, build, path_params]
case action
when 'download'
- download_namespace_project_build_artifacts_path(*args)
+ download_project_job_artifacts_path(*args)
when 'browse'
- browse_namespace_project_build_artifacts_path(*args)
+ browse_project_job_artifacts_path(*args)
when 'file'
- file_namespace_project_build_artifacts_path(*args)
+ file_project_job_artifacts_path(*args)
+ when 'raw'
+ raw_project_job_artifacts_path(*args)
end
end
- # Settings
- def project_settings_integrations_path(project, *args)
- namespace_project_settings_integrations_path(project.namespace, project, *args)
+ # Pipeline Schedules
+ def pipeline_schedules_path(project, *args)
+ project_pipeline_schedules_path(project, *args)
+ end
+
+ def pipeline_schedule_path(schedule, *args)
+ project = schedule.project
+ project_pipeline_schedule_path(project, schedule, *args)
end
- def project_settings_members_path(project, *args)
- namespace_project_settings_members_path(project.namespace, project, *args)
+ def edit_pipeline_schedule_path(schedule)
+ project = schedule.project
+ edit_project_pipeline_schedule_path(project, schedule)
end
- def project_settings_ci_cd_path(project, *args)
- namespace_project_settings_ci_cd_path(project.namespace, project, *args)
+ def take_ownership_pipeline_schedule_path(schedule, *args)
+ project = schedule.project
+ take_ownership_project_pipeline_schedule_path(project, schedule, *args)
end
end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index c2ab80f2e0d..c53ea4519da 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -3,7 +3,7 @@ module GraphHelper
refs = ""
# Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX)
# so anything leftover is internally used by GitLab
- commit_refs = commit.ref_names(repo).reject{ |name| name.starts_with?('refs/') }
+ commit_refs = commit.ref_names(repo).reject { |name| name.starts_with?('refs/') }
refs << commit_refs.join(' ')
# append note count
@@ -17,13 +17,10 @@ module GraphHelper
ids.zip(parent_spaces)
end
- def success_ratio(success_builds, failed_builds)
- failed_builds = failed_builds.count(:all)
- success_builds = success_builds.count(:all)
+ def success_ratio(counts)
+ return 100 if counts[:failed].zero?
- return 100 if failed_builds.zero?
-
- ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
+ ratio = (counts[:success].to_f / (counts[:success] + counts[:failed])) * 100
ratio.to_i
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index a6014088e92..4123a96911f 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -8,19 +8,20 @@ module GroupsHelper
group = Group.find_by_full_path(group)
end
- group.try(:avatar_url) || image_path('no_group_avatar.png')
+ group.try(:avatar_url) || ActionController::Base.helpers.image_path('no_group_avatar.png')
end
def group_title(group, name = nil, url = nil)
@has_group_title = true
full_title = ''
- group.ancestors.each do |parent|
- full_title += link_to(simple_sanitize(parent.name), group_path(parent), class: 'group-path hidable')
+ group.ancestors.reverse.each do |parent|
+ full_title += group_title_link(parent, hidable: true)
+
full_title += '<span class="hidable"> / </span>'.html_safe
end
- full_title += link_to(simple_sanitize(group.name), group_path(group), class: 'group-path')
+ full_title += group_title_link(group)
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
content_tag :span, class: 'group-title' do
@@ -56,4 +57,25 @@ module GroupsHelper
def group_issues(group)
IssuesFinder.new(current_user, group_id: group.id).execute
end
+
+ def remove_group_message(group)
+ _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
+ { group_name: group.name }
+ end
+
+ private
+
+ def group_title_link(group, hidable: false)
+ link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do
+ output =
+ if show_new_nav?
+ image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16)
+ else
+ ""
+ end
+
+ output << simple_sanitize(group.name)
+ output.html_safe
+ end
+ end
end
diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb
new file mode 100644
index 00000000000..551b9cca6b1
--- /dev/null
+++ b/app/helpers/hooks_helper.rb
@@ -0,0 +1,17 @@
+module HooksHelper
+ def link_to_test_hook(hook, trigger)
+ path = case hook
+ when ProjectHook
+ project = hook.project
+ test_project_hook_path(project, hook, trigger: trigger)
+ when SystemHook
+ test_admin_hook_path(hook, trigger: trigger)
+ end
+
+ trigger_human_name = trigger.to_s.tr('_', ' ').camelize
+
+ link_to path, rel: 'nofollow' do
+ content_tag(:span, trigger_human_name)
+ end
+ end
+end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 55fa81e95ef..9a404832423 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -1,4 +1,5 @@
module IconsHelper
+ extend self
include FontAwesome::Rails::IconHelper
# Creates an icon tag given icon name(s) and possible icon modifiers.
@@ -7,9 +8,10 @@ module IconsHelper
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
- if (options.keys & %w[aria-hidden aria-label]).empty?
- # Add `aria-hidden` if there are no aria's set
+ if (options.keys & %w[aria-hidden aria-label data-hidden]).empty?
+ # Add 'aria-hidden' and 'data-hidden' if they are not set in options.
options['aria-hidden'] = true
+ options['data-hidden'] = true
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
@@ -19,6 +21,8 @@ module IconsHelper
case names
when "standard"
names = "key"
+ when "two-factor"
+ names = "key"
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index a57b5a8fea5..a18ebfb6030 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -5,7 +5,7 @@ module ImportHelper
end
def provider_project_link(provider, path_with_namespace)
- url = __send__("#{provider}_project_url", path_with_namespace)
+ url = __send__("#{provider}_project_url", path_with_namespace) # rubocop:disable GitlabSecurity/PublicSend
link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer'
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 0b13dbf5f8d..197c90c4081 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -26,9 +26,9 @@ module IssuablesHelper
project = issuable.project
if issuable.is_a?(MergeRequest)
- namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
+ project_merge_request_path(project, issuable.iid, :json)
else
- namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
+ project_issue_path(project, issuable.iid, :json)
end
end
@@ -37,7 +37,10 @@ module IssuablesHelper
when Issue
IssueSerializer.new.represent(issuable).to_json
when MergeRequest
- MergeRequestSerializer.new.represent(issuable).to_json
+ MergeRequestSerializer
+ .new(current_user: current_user, project: issuable.project)
+ .represent(issuable)
+ .to_json
end
end
@@ -63,6 +66,17 @@ module IssuablesHelper
end
end
+ def users_dropdown_label(selected_users)
+ case selected_users.length
+ when 0
+ "Unassigned"
+ when 1
+ selected_users[0].name
+ else
+ "#{selected_users[0].name} + #{selected_users.length - 1} more"
+ end
+ end
+
def user_dropdown_label(user_id, default_label)
return default_label if user_id.nil?
return "Unassigned" if user_id == "0"
@@ -123,11 +137,9 @@ module IssuablesHelper
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
end
- if issuable.tasks?
- output << "&ensp;".html_safe
- output << content_tag(:span, issuable.task_status, id: "task_status", class: "hidden-xs hidden-sm")
- output << content_tag(:span, issuable.task_status_short, id: "task_status_short", class: "hidden-md hidden-lg")
- end
+ output << "&ensp;".html_safe
+ output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm")
+ output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg")
output
end
@@ -139,7 +151,7 @@ module IssuablesHelper
end
def issuable_labels_tooltip(labels, limit: 5)
- first, last = labels.partition.with_index{ |_, i| i < limit }
+ first, last = labels.partition.with_index { |_, i| i < limit }
label_names = first.collect(&:name)
label_names << "and #{last.size} more" unless last.empty?
@@ -153,11 +165,7 @@ module IssuablesHelper
}
state_title = titles[state] || state.to_s.humanize
-
- count =
- Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
- issuables_count_for_state(issuable_type, state)
- end
+ count = issuables_count_for_state(issuable_type, state)
html = content_tag(:span, state_title)
html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
@@ -166,7 +174,14 @@ module IssuablesHelper
end
def assigned_issuables_count(issuable_type)
- current_user.public_send("assigned_open_#{issuable_type}_count")
+ case issuable_type
+ when :issues
+ current_user.assigned_open_issues_count
+ when :merge_requests
+ current_user.assigned_open_merge_requests_count
+ else
+ raise ArgumentError, "invalid issuable `#{issuable_type}`"
+ end
end
def issuable_filter_params
@@ -187,40 +202,115 @@ module IssuablesHelper
issuable_filter_params.any? { |k| params.key?(k) }
end
- private
+ def issuable_initial_data(issuable)
+ data = {
+ endpoint: project_issue_path(@project, issuable),
+ canUpdate: can?(current_user, :update_issue, issuable),
+ canDestroy: can?(current_user, :destroy_issue, issuable),
+ canMove: current_user ? issuable.can_move?(current_user) : false,
+ issuableRef: issuable.to_reference,
+ isConfidential: issuable.confidential,
+ markdownPreviewUrl: preview_markdown_path(@project),
+ markdownDocs: help_page_path('user/markdown'),
+ projectsAutocompleteUrl: autocomplete_projects_path(project_id: @project.id),
+ issuableTemplates: issuable_templates(issuable),
+ projectPath: ref_project.path,
+ projectNamespace: ref_project.namespace.full_path,
+ initialTitleHtml: markdown_field(issuable, :title),
+ initialTitleText: issuable.title,
+ initialDescriptionHtml: markdown_field(issuable, :description),
+ initialDescriptionText: issuable.description,
+ initialTaskStatus: issuable.task_status
+ }
- def sidebar_gutter_collapsed?
- cookies[:collapsed_gutter] == 'true'
+ data.merge!(updated_at_by(issuable))
+
+ data.to_json
end
- def base_issuable_scope(issuable)
- issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
+ def updated_at_by(issuable)
+ return {} unless issuable.is_edited?
+
+ {
+ updatedAt: issuable.updated_at.to_time.iso8601,
+ updatedBy: {
+ name: issuable.last_edited_by.name,
+ path: user_path(issuable.last_edited_by)
+ }
+ }
end
- def issuable_state_scope(issuable)
- if issuable.respond_to?(:merged?) && issuable.merged?
- :merged
- else
- issuable.open? ? :opened : :closed
+ def issuables_count_for_state(issuable_type, state, finder: nil)
+ finder ||= public_send("#{issuable_type}_finder") # rubocop:disable GitlabSecurity/PublicSend
+ cache_key = finder.state_counter_cache_key
+
+ @counts ||= {}
+ @counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do
+ finder.count_by_state
end
+
+ @counts[cache_key][state]
end
- def issuables_count_for_state(issuable_type, state)
- @counts ||= {}
- @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
- @counts[issuable_type][state]
+ def close_issuable_url(issuable)
+ issuable_url(issuable, close_reopen_params(issuable, :close))
+ end
+
+ def reopen_issuable_url(issuable)
+ issuable_url(issuable, close_reopen_params(issuable, :reopen))
+ end
+
+ def close_reopen_issuable_url(issuable, should_inverse = false)
+ issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable)
+ end
+
+ def issuable_url(issuable, *options)
+ case issuable
+ when Issue
+ issue_url(issuable, *options)
+ when MergeRequest
+ merge_request_url(issuable, *options)
+ end
+ end
+
+ def issuable_button_visibility(issuable, closed)
+ case issuable
+ when Issue
+ issue_button_visibility(issuable, closed)
+ when MergeRequest
+ merge_request_button_visibility(issuable, closed)
+ end
+ end
+
+ def issuable_close_reopen_button_method(issuable)
+ case issuable
+ when Issue
+ ''
+ when MergeRequest
+ 'put'
+ end
end
- IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
- private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
+ def issuable_author_is_current_user(issuable)
+ issuable.author == current_user
+ end
+
+ def issuable_display_type(issuable)
+ issuable.model_name.human.downcase
+ end
+
+ private
- def issuables_state_counter_cache_key(issuable_type, state)
- opts = params.with_indifferent_access
- opts[:state] = state
- opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
- opts.delete_if { |_, value| value.blank? }
+ def sidebar_gutter_collapsed?
+ cookies[:collapsed_gutter] == 'true'
+ end
- hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
+ def issuable_state_scope(issuable)
+ if issuable.respond_to?(:merged?) && issuable.merged?
+ :merged
+ else
+ issuable.open? ? :opened : :closed
+ end
end
def issuable_templates(issuable)
@@ -230,8 +320,6 @@ module IssuablesHelper
issue_template_names
when MergeRequest
merge_request_template_names
- else
- raise 'Unknown issuable type!'
end
end
@@ -244,7 +332,7 @@ module IssuablesHelper
end
def selected_template(issuable)
- params[:issuable_template] if issuable_templates(issuable).any?{ |template| template[:name] == params[:issuable_template] }
+ params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end
def issuable_todo_button_data(issuable, todo, is_collapsed)
@@ -255,10 +343,28 @@ module IssuablesHelper
mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
issuable_id: issuable.id,
issuable_type: issuable.class.name.underscore,
- url: namespace_project_todos_path(@project.namespace, @project),
+ url: project_todos_path(@project),
delete_path: (dashboard_todo_path(todo) if todo),
placement: (is_collapsed ? 'left' : nil),
container: (is_collapsed ? 'body' : nil)
}
end
+
+ def close_reopen_params(issuable, action)
+ {
+ issuable.model_name.to_s.underscore => { state_event: action }
+ }.tap do |params|
+ params[:format] = :json if issuable.is_a?(Issue)
+ end
+ end
+
+ def issuable_sidebar_options(issuable, can_edit_issuable)
+ {
+ endpoint: "#{issuable_json_path(issuable)}?basic=true",
+ editable: can_edit_issuable,
+ currentUser: current_user.as_json(only: [:username, :id, :name], methods: :avatar_url),
+ rootPath: root_path,
+ fullPath: @project.full_path
+ }
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 82288f1da35..7e1ccb23e9e 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -17,10 +17,10 @@ module IssuesHelper
return '' if project.nil?
url =
- if options[:only_path]
- project.issues_tracker.issue_path(issue_iid)
+ if options[:internal]
+ url_for_internal_issue(issue_iid, project, options)
else
- project.issues_tracker.issue_url(issue_iid)
+ url_for_tracker_issue(issue_iid, project, options)
end
# Ensure we return a valid URL to prevent possible XSS.
@@ -29,6 +29,24 @@ module IssuesHelper
''
end
+ def url_for_tracker_issue(issue_iid, project, options)
+ if options[:only_path]
+ project.issues_tracker.issue_path(issue_iid)
+ else
+ project.issues_tracker.issue_url(issue_iid)
+ end
+ end
+
+ def url_for_internal_issue(issue_iid, project = @project, options = {})
+ helpers = Gitlab::Routing.url_helpers
+
+ if options[:only_path]
+ helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue_iid)
+ else
+ helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue_iid)
+ end
+ end
+
def bulk_update_milestone_options
milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
@@ -150,7 +168,7 @@ module IssuesHelper
Gitlab::UrlBuilder.build(single_discussion.first_note)
else
project = merge_request.project
- namespace_project_merge_request_path(project.namespace, project, merge_request)
+ project_merge_request_path(project, merge_request)
end
link_to link_text, path
@@ -158,4 +176,6 @@ module IssuesHelper
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
+ module_function :url_for_internal_issue
+ module_function :url_for_tracker_issue
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index e5b1e6e8bc7..e60513b35c7 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -43,11 +43,11 @@ module LabelsHelper
def label_filter_path(subject, label, type: :issue)
case subject
when Group
- send("#{type.to_s.pluralize}_group_path",
+ send("#{type.to_s.pluralize}_group_path", # rubocop:disable GitlabSecurity/PublicSend
subject,
label_name: [label.name])
when Project
- send("namespace_project_#{type.to_s.pluralize}_path",
+ send("namespace_project_#{type.to_s.pluralize}_path", # rubocop:disable GitlabSecurity/PublicSend
subject.namespace,
subject,
label_name: [label.name])
@@ -57,25 +57,24 @@ module LabelsHelper
def edit_label_path(label)
case label
when GroupLabel then edit_group_label_path(label.group, label)
- when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label)
+ when ProjectLabel then edit_project_label_path(label.project, label)
end
end
def destroy_label_path(label)
case label
when GroupLabel then group_label_path(label.group, label)
- when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label)
+ when ProjectLabel then project_label_path(label.project, label)
end
end
def render_colored_label(label, label_suffix = '', tooltip: true)
- label_color = label.color || Label::DEFAULT_COLOR
- text_color = text_color_for_bg(label_color)
+ text_color = text_color_for_bg(label.color)
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
- %(style="background-color: #{label_color}; color: #{text_color}" ) +
+ %(style="background-color: #{label.color}; color: #{text_color}" ) +
%(title="#{escape_once(label.description)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
@@ -128,27 +127,34 @@ module LabelsHelper
project = @target_project || @project
if project
- namespace_project_labels_path(project.namespace, project, :json)
+ project_labels_path(project, :json)
else
dashboard_labels_path(:json)
end
end
+ def can_subscribe_to_label_in_different_levels?(label)
+ defined?(@project) && label.is_a?(GroupLabel)
+ end
+
def label_subscription_status(label, project)
- return 'project-level' if label.subscribed?(current_user, project)
return 'group-level' if label.subscribed?(current_user)
+ return 'project-level' if label.subscribed?(current_user, project)
'unsubscribed'
end
- def group_label_unsubscribe_path(label, project)
+ def toggle_subscription_label_path(label, project)
+ return toggle_subscription_group_label_path(label.group, label) unless project
+
case label_subscription_status(label, project)
- when 'project-level' then toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)
when 'group-level' then toggle_subscription_group_label_path(label.group, label)
+ when 'project-level' then toggle_subscription_project_label_path(project, label)
+ when 'unsubscribed' then toggle_subscription_project_label_path(project, label)
end
end
- def label_subscription_toggle_button_text(label, project)
+ def label_subscription_toggle_button_text(label, project = nil)
label.subscribed?(current_user, project) ? 'Unsubscribe' : 'Subscribe'
end
diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb
new file mode 100644
index 00000000000..2c5619ac41b
--- /dev/null
+++ b/app/helpers/lazy_image_tag_helper.rb
@@ -0,0 +1,24 @@
+module LazyImageTagHelper
+ def placeholder_image
+ "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+ end
+
+ # Override the default ActionView `image_tag` helper to support lazy-loading
+ def image_tag(source, options = {})
+ options = options.symbolize_keys
+
+ unless options.delete(:lazy) == false
+ options[:data] ||= {}
+ options[:data][:src] = path_to_image(source)
+ options[:class] ||= ""
+ options[:class] << " lazy"
+
+ source = placeholder_image
+ end
+
+ super(source, options)
+ end
+
+ # Required for Banzai::Filter::ImageLazyLoadFilter
+ module_function :placeholder_image
+end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 0781874d7fc..941cfce8370 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -1,6 +1,9 @@
require 'nokogiri'
module MarkupHelper
+ include ActionView::Helpers::TagHelper
+ include ActionView::Context
+
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
end
@@ -32,7 +35,7 @@ module MarkupHelper
context = {
project: @project,
current_user: (current_user if defined?(current_user)),
- pipeline: :single_line,
+ pipeline: :single_line
}
gfm_body = Banzai.render(body, context)
@@ -74,7 +77,7 @@ module MarkupHelper
context[:project] ||= @project
html = markdown_unsafe(text, context)
- banzai_postprocess(html, context)
+ prepare_for_rendering(html, context)
end
def markdown_field(object, field)
@@ -82,13 +85,13 @@ module MarkupHelper
return '' unless object.present?
html = Banzai.render_field(object, field)
- banzai_postprocess(html, object.banzai_render_context(field))
+ prepare_for_rendering(html, object.banzai_render_context(field))
end
def markup(file_name, text, context = {})
context[:project] ||= @project
html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
- banzai_postprocess(html, context)
+ prepare_for_rendering(html, context)
end
def render_wiki_content(wiki_page)
@@ -107,22 +110,22 @@ module MarkupHelper
wiki_page.formatted_content.html_safe
end
- banzai_postprocess(html, context)
+ prepare_for_rendering(html, context)
end
def markup_unsafe(file_name, text, context = {})
return '' unless text.present?
if gitlab_markdown?(file_name)
- Hamlit::RailsHelpers.preserve(markdown_unsafe(text, context))
+ markdown_unsafe(text, context)
elsif asciidoc?(file_name)
- asciidoc_unsafe(text)
+ asciidoc_unsafe(text, context)
elsif plain?(file_name)
content_tag :pre, class: 'plain-readme' do
text
end
else
- other_markup_unsafe(file_name, text)
+ other_markup_unsafe(file_name, text, context)
end
rescue RuntimeError
simple_format(text)
@@ -217,16 +220,15 @@ module MarkupHelper
Banzai.render(text, context)
end
- def asciidoc_unsafe(text)
- Gitlab::Asciidoc.render(text)
+ def asciidoc_unsafe(text, context = {})
+ Gitlab::Asciidoc.render(text, context)
end
- def other_markup_unsafe(file_name, text)
- Gitlab::OtherMarkup.render(file_name, text)
+ def other_markup_unsafe(file_name, text, context = {})
+ Gitlab::OtherMarkup.render(file_name, text, context)
end
- # Calls Banzai.post_process with some common context options
- def banzai_postprocess(html, context = {})
+ def prepare_for_rendering(html, context = {})
return '' unless html.present?
context.merge!(
@@ -239,7 +241,9 @@ module MarkupHelper
requested_path: @path
)
- Banzai.post_process(html, context)
+ html = Banzai.post_process(html, context)
+
+ Hamlit::RailsHelpers.preserve(html)
end
extend self
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index e347f61fb8d..c31023f2d9a 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,8 +1,7 @@
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
- target_project = event.project.forked_from_project || event.project
- new_namespace_project_merge_request_path(
- event.project.namespace,
+ target_project = event.project.default_merge_request_target
+ project_new_merge_request_path(
event.project,
new_mr_from_push_event(event, target_project)
)
@@ -19,14 +18,6 @@ module MergeRequestsHelper
}
end
- def mr_widget_refresh_url(mr)
- if mr && mr.target_project
- merge_widget_refresh_namespace_project_merge_request_url(mr.target_project.namespace, mr.target_project, mr)
- else
- ''
- end
- end
-
def mr_css_classes(mr)
classes = "merge-request"
classes << " closed" if mr.closed?
@@ -49,71 +40,25 @@ module MergeRequestsHelper
def merge_path_description(merge_request, separator)
if merge_request.for_fork?
- "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
+ "Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.full_path}:#{@merge_request.target_branch}"
else
"Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
end
end
- def issues_sentence(issues)
- # Issuable sorter will sort local issues, then issues from the same
- # namespace, then all other issues.
- issues = Gitlab::IssuableSorter.sort(@project, issues).map do |issue|
- issue.to_reference(@project)
- end
- issues.to_sentence
- end
-
- def mr_closes_issues
- @mr_closes_issues ||= @merge_request.closes_issues(current_user)
- end
-
- def mr_issues_mentioned_but_not_closing
- @mr_issues_mentioned_but_not_closing ||= @merge_request.issues_mentioned_but_not_closing(current_user)
- end
-
def mr_change_branches_path(merge_request)
- new_namespace_project_merge_request_path(
- @project.namespace, @project,
+ project_new_merge_request_path(
+ @project,
merge_request: {
source_project_id: merge_request.source_project_id,
target_project_id: merge_request.target_project_id,
source_branch: merge_request.source_branch,
- target_branch: merge_request.target_branch,
+ target_branch: merge_request.target_branch
},
change_branches: true
)
end
- def mr_assign_issues_link
- issues = MergeRequests::AssignIssuesService.new(@project,
- current_user,
- merge_request: @merge_request,
- closes_issues: mr_closes_issues
- ).assignable_issues
- path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- if issues.present?
- pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
- link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
- end
- end
-
- def source_branch_with_namespace(merge_request)
- namespace = merge_request.source_project_namespace
- branch = merge_request.source_branch
-
- if merge_request.source_branch_exists?
- namespace = link_to(namespace, project_path(merge_request.source_project))
- branch = link_to(branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
- end
-
- if merge_request.for_fork?
- namespace + ":" + branch
- else
- branch
- end
- end
-
def format_mr_branch_names(merge_request)
source_path = merge_request.source_project_path
target_path = merge_request.target_project_path
@@ -127,14 +72,16 @@ module MergeRequestsHelper
end
end
+ def target_projects(project)
+ [project, project.default_merge_request_target].uniq
+ end
+
def merge_request_button_visibility(merge_request, closed)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end
def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
- diffs_namespace_project_merge_request_path(
- project.namespace, project, merge_request,
- diff_id: merge_request_diff.id, start_sha: start_sha)
+ diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha)
end
def version_index(merge_request_diff)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index c9e70faa52e..86666022a2a 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -1,7 +1,7 @@
module MilestonesHelper
def milestones_filter_path(opts = {})
if @project
- namespace_project_milestones_path(@project.namespace, @project, opts)
+ project_milestones_path(@project, opts)
elsif @group
group_milestones_path(@group, opts)
else
@@ -11,7 +11,7 @@ module MilestonesHelper
def milestones_label_path(opts = {})
if @project
- namespace_project_issues_path(@project.namespace, @project, opts)
+ project_issues_path(@project, opts)
elsif @group
issues_group_path(@group, opts)
else
@@ -32,7 +32,18 @@ module MilestonesHelper
end
def milestone_issues_by_label_count(milestone, label, state:)
- milestone.issues.with_label(label.title).send(state).size
+ issues = milestone.issues.with_label(label.title)
+ issues =
+ case state
+ when :opened
+ issues.opened
+ when :closed
+ issues.closed
+ else
+ raise ArgumentError, "invalid milestone state `#{state}`"
+ end
+
+ issues.size
end
# Returns count of milestones for different states
@@ -54,8 +65,10 @@ module MilestonesHelper
def milestone_class_for_state(param, check, match_blank_param = false)
if match_blank_param
'active' if param.blank? || param == check
+ elsif param == check
+ 'active'
else
- 'active' if param == check
+ check
end
end
@@ -73,7 +86,9 @@ module MilestonesHelper
def milestones_filter_dropdown_path
project = @target_project || @project
if project
- namespace_project_milestones_path(project.namespace, project, :json)
+ project_milestones_path(project, :json)
+ elsif @group
+ group_milestones_path(@group, :json)
else
dashboard_milestones_path(:json)
end
@@ -115,4 +130,44 @@ module MilestonesHelper
end
end
end
+
+ def milestone_merge_request_tab_path(milestone)
+ if @project
+ merge_requests_project_milestone_path(@project, milestone, format: :json)
+ elsif @group
+ merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
+ else
+ merge_requests_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
+ end
+ end
+
+ def milestone_participants_tab_path(milestone)
+ if @project
+ participants_project_milestone_path(@project, milestone, format: :json)
+ elsif @group
+ participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
+ else
+ participants_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
+ end
+ end
+
+ def milestone_labels_tab_path(milestone)
+ if @project
+ labels_project_milestone_path(@project, milestone, format: :json)
+ elsif @group
+ labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
+ else
+ labels_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
+ end
+ end
+
+ def group_milestone_route(milestone, params = {})
+ params = nil if params.empty?
+
+ if milestone.is_legacy_group_milestone?
+ group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: params)
+ else
+ group_milestone_path(@group, milestone.iid, milestone: params)
+ end
+ end
end
diff --git a/app/helpers/milestones_routing_helper.rb b/app/helpers/milestones_routing_helper.rb
new file mode 100644
index 00000000000..766d5262018
--- /dev/null
+++ b/app/helpers/milestones_routing_helper.rb
@@ -0,0 +1,17 @@
+module MilestonesRoutingHelper
+ def milestone_path(milestone, *args)
+ if milestone.is_group_milestone?
+ group_milestone_path(milestone.group, milestone, *args)
+ elsif milestone.is_project_milestone?
+ project_milestone_path(milestone.project, milestone, *args)
+ end
+ end
+
+ def milestone_url(milestone, *args)
+ if milestone.is_group_milestone?
+ group_milestone_url(milestone.group, milestone, *args)
+ elsif milestone.is_project_milestone?
+ project_milestone_url(milestone.project, milestone, *args)
+ end
+ end
+end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 17bfd07e00f..b63b3b70903 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,42 +1,50 @@
module NavHelper
+ def page_with_sidebar_class
+ class_name = page_gutter_class
+ class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
+ class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar
+
+ class_name
+ end
+
def page_gutter_class
if current_path?('merge_requests#show') ||
- current_path?('merge_requests#diffs') ||
- current_path?('merge_requests#commits') ||
- current_path?('merge_requests#builds') ||
- current_path?('merge_requests#conflicts') ||
- current_path?('merge_requests#pipelines') ||
+ current_path?('projects/merge_requests/conflicts#show') ||
current_path?('issues#show') ||
current_path?('milestones#show')
if cookies[:collapsed_gutter] == 'true'
- "page-gutter right-sidebar-collapsed"
+ %w[page-gutter right-sidebar-collapsed]
else
- "page-gutter right-sidebar-expanded"
+ %w[page-gutter right-sidebar-expanded]
end
- elsif current_path?('builds#show')
- "page-gutter build-sidebar right-sidebar-expanded"
+ elsif current_path?('jobs#show')
+ %w[page-gutter build-sidebar right-sidebar-expanded]
elsif current_path?('wikis#show') ||
current_path?('wikis#edit') ||
current_path?('wikis#update') ||
current_path?('wikis#history') ||
current_path?('wikis#git_access')
- "page-gutter wiki-sidebar right-sidebar-expanded"
+ %w[page-gutter wiki-sidebar right-sidebar-expanded]
+ else
+ []
end
end
def nav_header_class
- class_name = ''
- class_name << " with-horizontal-nav" if defined?(nav) && nav
+ class_names = []
+ class_names << 'with-horizontal-nav' if defined?(nav) && nav
- class_name
+ class_names
end
def layout_nav_class
- class_name = ''
- class_name << " page-with-layout-nav" if defined?(nav) && nav
- class_name << " page-with-sub-nav" if content_for?(:sub_nav)
+ return [] if show_new_nav?
- class_name
+ class_names = []
+ class_names << 'page-with-layout-nav' if defined?(nav) && nav
+ class_names << 'page-with-sub-nav' if content_for?(:sub_nav)
+
+ class_names
end
def nav_control_class
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index eab0738a368..e857e837c16 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -10,8 +10,8 @@ module NotesHelper
Ability.can_edit_note?(current_user, note)
end
- def note_supports_slash_commands?(note)
- Notes::SlashCommandsService.supported?(note, current_user)
+ def note_supports_quick_actions?(note)
+ Notes::QuickActionsService.supported?(note, current_user)
end
def noteable_json(noteable)
@@ -19,7 +19,7 @@ module NotesHelper
id: noteable.id,
class: noteable.class.name,
resources: noteable.class.table_name,
- project_id: noteable.project.id,
+ project_id: noteable.project.id
}.to_json
end
@@ -34,7 +34,7 @@ module NotesHelper
data = {
line_code: line_code,
- line_type: line_type,
+ line_type: line_type
}
if @use_legacy_diff_notes
@@ -47,10 +47,26 @@ module NotesHelper
data
end
+ def add_diff_note_button(line_code, position, line_type)
+ return if @diff_notes_disabled
+
+ button_tag '',
+ class: 'add-diff-note js-add-diff-note-button',
+ type: 'submit', name: 'button',
+ data: diff_view_line_data(line_code, position, line_type),
+ title: 'Add a comment to this line' do
+ icon('comment-o')
+ end
+ end
+
def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user
- data = { discussion_id: discussion.id, line_type: line_type }
+ data = {
+ discussion_id: discussion.reply_id,
+ discussion_project_id: discussion.project&.id,
+ line_type: line_type
+ }
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply'
@@ -60,24 +76,72 @@ module NotesHelper
note.project.team.human_max_access(note.author_id)
end
- def discussion_diff_path(discussion)
- if discussion.for_merge_request? && discussion.diff_discussion?
- if discussion.active?
- # Without a diff ID, the link always points to the latest diff version
- diff_id = nil
- elsif merge_request_diff = discussion.latest_merge_request_diff
- diff_id = merge_request_diff.id
- else
- # If the discussion is not active, and we cannot find the latest
- # merge request diff for this discussion, we return no path at all.
- return
- end
-
- diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, diff_id: diff_id, anchor: discussion.line_code)
+ def discussion_path(discussion)
+ if discussion.for_merge_request?
+ return unless discussion.diff_discussion?
+
+ version_params = discussion.merge_request_version_params
+ return unless version_params
+
+ path_params = version_params.merge(anchor: discussion.line_code)
+
+ diffs_project_merge_request_path(discussion.project, discussion.noteable, path_params)
elsif discussion.for_commit?
anchor = discussion.line_code if discussion.diff_discussion?
- namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor)
+ project_commit_path(discussion.project, discussion.noteable, anchor: anchor)
+ end
+ end
+
+ def notes_url
+ if @snippet.is_a?(PersonalSnippet)
+ snippet_notes_path(@snippet)
+ else
+ project_noteable_notes_path(@project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)
+ end
+ end
+
+ def note_url(note, project = @project)
+ if note.noteable.is_a?(PersonalSnippet)
+ snippet_note_path(note.noteable, note)
+ else
+ project_note_path(project, note)
end
end
+
+ def noteable_note_url(note)
+ Gitlab::UrlBuilder.build(note)
+ end
+
+ def form_resources
+ if @snippet.is_a?(PersonalSnippet)
+ [@note]
+ else
+ [@project.namespace.becomes(Namespace), @project, @note]
+ end
+ end
+
+ def new_form_url
+ return nil unless @snippet.is_a?(PersonalSnippet)
+
+ snippet_notes_path(@snippet)
+ end
+
+ def can_create_note?
+ if @snippet.is_a?(PersonalSnippet)
+ can?(current_user, :comment_personal_snippet, @snippet)
+ else
+ can?(current_user, :create_note, @project)
+ end
+ end
+
+ def initial_notes_data(autocomplete)
+ {
+ notesUrl: notes_url,
+ notesIds: @notes.map(&:id),
+ now: Time.now.to_i,
+ diffView: diff_view,
+ autocomplete: autocomplete
+ }
+ end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 03cc8f2b6bd..fde961e2da4 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -21,30 +21,36 @@ module NotificationsHelper
end
def notification_title(level)
+ # Can be anything in `NotificationSetting.level:
case level.to_sym
when :participating
- 'Participate'
+ s_('NotificationLevel|Participate')
when :mention
- 'On mention'
+ s_('NotificationLevel|On mention')
else
- level.to_s.titlecase
+ N_('NotificationLevel|Global')
+ N_('NotificationLevel|Watch')
+ N_('NotificationLevel|Disabled')
+ N_('NotificationLevel|Custom')
+ level = "NotificationLevel|#{level.to_s.humanize}"
+ s_(level)
end
end
def notification_description(level)
case level.to_sym
when :participating
- 'You will only receive notifications for threads you have participated in'
+ _('You will only receive notifications for threads you have participated in')
when :mention
- 'You will receive notifications only for comments in which you were @mentioned'
+ _('You will receive notifications only for comments in which you were @mentioned')
when :watch
- 'You will receive notifications for any activity'
+ _('You will receive notifications for any activity')
when :disabled
- 'You will not get any notifications via email'
+ _('You will not get any notifications via email')
when :global
- 'Use your global notification setting'
+ _('Use your global notification setting')
when :custom
- 'You will only receive notifications for the events you choose'
+ _('You will only receive notifications for the events you choose')
end
end
@@ -76,11 +82,22 @@ module NotificationsHelper
end
def notification_event_name(event)
+ # All values from NotificationSetting::EMAIL_EVENTS
case event
when :success_pipeline
- 'Successful pipeline'
+ s_('NotificationEvent|Successful pipeline')
else
- event.to_s.humanize
+ N_('NotificationEvent|New note')
+ N_('NotificationEvent|New issue')
+ N_('NotificationEvent|Reopen issue')
+ N_('NotificationEvent|Close issue')
+ N_('NotificationEvent|Reassign issue')
+ N_('NotificationEvent|New merge request')
+ N_('NotificationEvent|Close merge request')
+ N_('NotificationEvent|Reassign merge request')
+ N_('NotificationEvent|Merge merge request')
+ N_('NotificationEvent|Failed pipeline')
+ s_(event.to_s.humanize)
end
end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 3286a92a8a7..b30b2eb1d03 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -4,6 +4,10 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any?
+ if show_new_nav? && titles.any? && !defined?(@breadcrumb_title)
+ @breadcrumb_title = @page_title.last
+ end
+
# Segments are seperated by middot
@page_title.join(" \u00b7 ")
end
diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb
new file mode 100644
index 00000000000..83dd76a01dd
--- /dev/null
+++ b/app/helpers/pagination_helper.rb
@@ -0,0 +1,21 @@
+module PaginationHelper
+ def paginate_collection(collection, remote: nil)
+ if collection.is_a?(Kaminari::PaginatableWithoutCount)
+ paginate_without_count(collection)
+ elsif collection.respond_to?(:total_pages)
+ paginate_with_count(collection, remote: remote)
+ end
+ end
+
+ def paginate_without_count(collection)
+ render(
+ 'kaminari/gitlab/without_count',
+ previous_path: path_to_prev_page(collection),
+ next_path: path_to_next_page(collection)
+ )
+ end
+
+ def paginate_with_count(collection, remote: nil)
+ paginate(collection, remote: remote, theme: 'gitlab')
+ end
+end
diff --git a/app/helpers/performance_bar_helper.rb b/app/helpers/performance_bar_helper.rb
new file mode 100644
index 00000000000..d24efe37f5f
--- /dev/null
+++ b/app/helpers/performance_bar_helper.rb
@@ -0,0 +1,7 @@
+module PerformanceBarHelper
+ # This is a hack since using `alias_method :performance_bar_enabled?, :peek_enabled?`
+ # in WithPerformanceBar breaks tests (but works in the browser).
+ def performance_bar_enabled?
+ peek_enabled?
+ end
+end
diff --git a/app/helpers/pipeline_schedules_helper.rb b/app/helpers/pipeline_schedules_helper.rb
new file mode 100644
index 00000000000..6edaf78de1b
--- /dev/null
+++ b/app/helpers/pipeline_schedules_helper.rb
@@ -0,0 +1,11 @@
+module PipelineSchedulesHelper
+ def timezone_data
+ ActiveSupport::TimeZone.all.map do |timezone|
+ {
+ name: timezone.name,
+ offset: timezone.utc_offset,
+ identifier: timezone.tzinfo.identifier
+ }
+ end
+ end
+end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index de959f13713..d36bb4ab074 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -49,7 +49,7 @@ module PreferencesHelper
user_view = current_user.project_view
- if @project.feature_available?(:repository, current_user)
+ if can?(current_user, :download_code, @project)
user_view
elsif user_view == "activity"
"activity"
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
new file mode 100644
index 00000000000..45238f12ac7
--- /dev/null
+++ b/app/helpers/profiles_helper.rb
@@ -0,0 +1,7 @@
+module ProfilesHelper
+ def email_provider_label
+ return unless current_user.external_email?
+
+ current_user.email_provider.present? ? Gitlab::OAuth::Provider.label_for(current_user.email_provider) : "LDAP"
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8c26348a975..bee4950e414 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -58,7 +58,17 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
- project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
+ project_link = link_to project_path(project), { class: "project-item-select-holder" } do
+ output =
+ if show_new_nav?
+ project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16)
+ else
+ ""
+ end
+
+ output << simple_sanitize(project.name)
+ output.html_safe
+ end
if current_user
project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
@@ -70,21 +80,30 @@ module ProjectsHelper
end
def remove_project_message(project)
- "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
+ _("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
+ { project_name_with_namespace: project.name_with_namespace }
end
def transfer_project_message(project)
- "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
+ _("You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?") %
+ { project_name_with_namespace: project.name_with_namespace }
end
def remove_fork_project_message(project)
- "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?"
+ _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") %
+ { forked_from_project: @project.forked_from_project.name_with_namespace }
end
def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
end
+ def project_search_tabs?(tab)
+ abilities = Array(search_tab_ability_map[tab])
+
+ abilities.any? { |ability| can?(current_user, ability, @project) }
+ end
+
def project_nav_tab?(name)
project_nav_tabs.include? name
end
@@ -110,15 +129,13 @@ module ProjectsHelper
end
def license_short_name(project)
- return 'LICENSE' if project.repository.license_key.nil?
-
- license = Licensee::License.new(project.repository.license_key)
-
- license.nickname || license.name
+ license = project.repository.license
+ license&.nickname || license&.name || 'LICENSE'
end
def last_push_event
return unless current_user
+ return current_user.recent_push unless @project
project_ids = [@project.id]
if fork = current_user.fork_of(@project)
@@ -132,43 +149,103 @@ module ProjectsHelper
# Don't show option "everyone with access" if project is private
options = project_feature_options
+ level = @project.project_feature.public_send(field) # rubocop:disable GitlabSecurity/PublicSend
+
if @project.private?
- level = @project.project_feature.send(field)
- options.delete('Everyone with access')
- highest_available_option = options.values.max if level == ProjectFeature::ENABLED
+ disabled_option = ProjectFeature::ENABLED
+ highest_available_option = ProjectFeature::PRIVATE if level == disabled_option
end
- options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
+ options = options_for_select(
+ options.invert,
+ selected: highest_available_option || level,
+ disabled: disabled_option
+ )
- content_tag(
- :select,
- options,
- name: "project[project_feature_attributes][#{field}]",
- id: "project_project_feature_attributes_#{field}",
- class: "pull-right form-control #{repo_children_classes(field)}",
- data: { field: field }
- ).html_safe
+ content_tag :div, class: "select-wrapper" do
+ concat(
+ content_tag(
+ :select,
+ options,
+ name: "project[project_feature_attributes][#{field}]",
+ id: "project_project_feature_attributes_#{field}",
+ class: "pull-right form-control select-control #{repo_children_classes(field)} ",
+ data: { field: field }
+ )
+ )
+ concat(
+ icon('chevron-down')
+ )
+ end.html_safe
end
def link_to_autodeploy_doc
- link_to 'About auto deploy', help_page_path('ci/autodeploy/index'), target: '_blank'
+ link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
end
def autodeploy_flash_notice(branch_name)
- "Branch <strong>#{truncate(sanitize(branch_name))}</strong> was created. To set up auto deploy, \
- choose a GitLab CI Yaml template and commit your changes. #{link_to_autodeploy_doc}".html_safe
+ translation = _("Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}") %
+ { branch_name: truncate(sanitize(branch_name)), link_to_autodeploy_doc: link_to_autodeploy_doc }
+ translation.html_safe
end
def project_list_cache_key(project)
- key = [project.namespace.cache_key, project.cache_key, controller.controller_name, controller.action_name, current_application_settings.cache_key, 'v2.4']
+ key = [
+ project.route.cache_key,
+ project.cache_key,
+ controller.controller_name,
+ controller.action_name,
+ current_application_settings.cache_key,
+ 'v2.5'
+ ]
+
key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?
key
end
def load_pipeline_status(projects)
- Gitlab::Cache::Ci::ProjectPipelineStatus.
- load_in_batch_for_projects(projects)
+ Gitlab::Cache::Ci::ProjectPipelineStatus
+ .load_in_batch_for_projects(projects)
+ end
+
+ def show_no_ssh_key_message?
+ cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+ end
+
+ def show_no_password_message?
+ cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
+ ( current_user.require_password_creation? || current_user.require_personal_access_token_creation_for_git_auth? )
+ end
+
+ def link_to_set_password
+ if current_user.require_password_creation?
+ link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
+ else
+ link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
+ end
+ end
+
+ # Returns true if any projects are present.
+ #
+ # If the relation has a LIMIT applied we'll cast the relation to an Array
+ # since repeated any? checks would otherwise result in multiple COUNT queries
+ # being executed.
+ #
+ # If no limit is applied we'll just issue a COUNT since the result set could
+ # be too large to load into memory.
+ def any_projects?(projects)
+ return projects.any? if projects.is_a?(Array)
+
+ if projects.limit_value
+ projects.to_a.any?
+ else
+ projects.except(:offset).any?
+ end
+ end
+
+ def has_projects_or_name?(projects, params)
+ !!(params[:name] || any_projects?(projects))
end
private
@@ -198,18 +275,9 @@ module ProjectsHelper
nav_tabs << :container_registry
end
- tab_ability_map = {
- environments: :read_environment,
- milestones: :read_milestone,
- pipelines: :read_pipeline,
- snippets: :read_project_snippet,
- settings: :admin_project,
- builds: :read_build,
- labels: :read_label,
- issues: :read_issue,
- team: :read_project_member,
- wiki: :read_wiki
- }
+ if project.builds_enabled? && can?(current_user, :read_pipeline, project)
+ nav_tabs << :pipelines
+ end
tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project)
@@ -220,14 +288,37 @@ module ProjectsHelper
nav_tabs.flatten
end
+ def tab_ability_map
+ {
+ environments: :read_environment,
+ milestones: :read_milestone,
+ snippets: :read_project_snippet,
+ settings: :admin_project,
+ builds: :read_build,
+ labels: :read_label,
+ issues: :read_issue,
+ project_members: :read_project_member,
+ wiki: :read_wiki
+ }
+ end
+
+ def search_tab_ability_map
+ @search_tab_ability_map ||= tab_ability_map.merge(
+ blobs: :download_code,
+ commits: :download_code,
+ merge_requests: :read_merge_request,
+ notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet]
+ )
+ end
+
def project_lfs_status(project)
if project.lfs_enabled?
content_tag(:span, class: 'lfs-enabled') do
- 'Enabled'
+ s_('LFSStatus|Enabled')
end
else
content_tag(:span, class: 'lfs-disabled') do
- 'Disabled'
+ s_('LFSStatus|Disabled')
end
end
end
@@ -236,7 +327,7 @@ module ProjectsHelper
if current_user
current_user.name
else
- "Your name"
+ _("Your name")
end
end
@@ -253,7 +344,7 @@ module ProjectsHelper
when 'ssh'
project.ssh_url_to_repo
else
- project.http_url_to_repo(current_user)
+ project.http_url_to_repo
end
end
@@ -273,25 +364,24 @@ module ProjectsHelper
if project.last_activity_at
time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
else
- "Never"
+ s_("ProjectLastActivity|Never")
end
end
def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
- namespace_project_new_blob_path(
- project.namespace,
+ commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase }
+ project_new_blob_path(
project,
project.default_branch || 'master',
file_name: file_name,
- commit_message: commit_message || "Add #{file_name.downcase}",
+ commit_message: commit_message,
branch_name: branch_name,
context: context
)
end
def add_koding_stack_path(project)
- namespace_project_new_blob_path(
- project.namespace,
+ project_new_blob_path(
project,
project.default_branch || 'master',
file_name: '.koding.yml',
@@ -331,7 +421,7 @@ module ProjectsHelper
if project
import_path = "/Home/Stacks/import"
- repo = project.path_with_namespace
+ repo = project.full_path
branch ||= project.default_branch
sha ||= project.commit.short_id
@@ -345,8 +435,7 @@ module ProjectsHelper
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
- namespace_project_blob_path(
- project.namespace,
+ project_blob_path(
project,
tree_join(project.default_branch,
contribution_guide.name)
@@ -376,7 +465,7 @@ module ProjectsHelper
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
- namespace_project_wiki_path(proj.namespace, proj, page, url_params)
+ project_wiki_path(proj, page, url_params)
end
def project_status_css_class(status)
@@ -392,7 +481,7 @@ module ProjectsHelper
def readme_cache_key
sha = @project.commit.try(:sha) || 'nil'
- [@project.path_with_namespace, sha, "readme"].join('-')
+ [@project.full_path, sha, "readme"].join('-')
end
def current_ref
@@ -400,9 +489,8 @@ module ProjectsHelper
end
def filename_path(project, filename)
- if project && blob = project.repository.send(filename)
- namespace_project_blob_path(
- project.namespace,
+ if project && blob = project.repository.public_send(filename) # rubocop:disable GitlabSecurity/PublicSend
+ project_blob_path(
project,
tree_join(project.default_branch, blob.name)
)
@@ -420,9 +508,9 @@ module ProjectsHelper
def project_feature_options
{
- 'Disabled' => ProjectFeature::DISABLED,
- 'Only team members' => ProjectFeature::PRIVATE,
- 'Everyone with access' => ProjectFeature::ENABLED
+ ProjectFeature::DISABLED => s_('ProjectFeature|Disabled'),
+ ProjectFeature::PRIVATE => s_('ProjectFeature|Only team members'),
+ ProjectFeature::ENABLED => s_('ProjectFeature|Everyone with access')
}
end
@@ -453,4 +541,12 @@ module ProjectsHelper
current_application_settings.restricted_visibility_levels || []
end
+
+ def find_file_path
+ return unless @project && !@project.empty_repo?
+
+ ref = @ref || @project.repository.root_ref
+
+ project_find_file_path(@project, ref)
+ end
end
diff --git a/app/helpers/rss_helper.rb b/app/helpers/rss_helper.rb
index ea5d2932ef4..9ac4df88dc3 100644
--- a/app/helpers/rss_helper.rb
+++ b/app/helpers/rss_helper.rb
@@ -1,5 +1,5 @@
module RssHelper
def rss_url_options
- { format: :atom, private_token: current_user.try(:private_token) }
+ { format: :atom, rss_token: current_user.try(:rss_token) }
end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8ff8db16514..ae0e0aa3cf9 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -42,7 +42,7 @@ module SearchHelper
{ category: "Settings", label: "User settings", url: profile_path },
{ category: "Settings", label: "SSH Keys", url: profile_keys_path },
{ category: "Settings", label: "Dashboard", url: root_path },
- { category: "Settings", label: "Admin Section", url: admin_root_path },
+ { category: "Settings", label: "Admin Section", url: admin_root_path }
]
end
@@ -57,7 +57,7 @@ module SearchHelper
{ category: "Help", label: "SSH Keys Help", url: help_page_path("ssh/README") },
{ category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks/system_hooks") },
{ category: "Help", label: "Webhooks Help", url: help_page_path("user/project/integrations/webhooks") },
- { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") },
+ { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") }
]
end
@@ -67,16 +67,16 @@ module SearchHelper
ref = @ref || @project.repository.root_ref
[
- { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
- { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
- { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
- { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
- { category: "Current Project", label: "Members", url: namespace_project_settings_members_path(@project.namespace, @project) },
- { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Files", url: project_tree_path(@project, ref) },
+ { category: "Current Project", label: "Commits", url: project_commits_path(@project, ref) },
+ { category: "Current Project", label: "Network", url: project_network_path(@project, ref) },
+ { category: "Current Project", label: "Graph", url: project_graph_path(@project, ref) },
+ { category: "Current Project", label: "Issues", url: project_issues_path(@project) },
+ { category: "Current Project", label: "Merge Requests", url: project_merge_requests_path(@project) },
+ { category: "Current Project", label: "Milestones", url: project_milestones_path(@project) },
+ { category: "Current Project", label: "Snippets", url: project_snippets_path(@project) },
+ { category: "Current Project", label: "Members", url: project_project_members_path(@project) },
+ { category: "Current Project", label: "Wiki", url: project_wikis_path(@project) }
]
else
[]
@@ -97,14 +97,14 @@ module SearchHelper
# Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5)
- current_user.authorized_projects.search_by_title(term).
- sorted_by_stars.non_archived.limit(limit).map do |p|
+ current_user.authorized_projects.search_by_title(term)
+ .sorted_by_stars.non_archived.limit(limit).map do |p|
{
category: "Projects",
id: p.id,
value: "#{search_result_sanitize(p.name)}",
label: "#{search_result_sanitize(p.name_with_namespace)}",
- url: namespace_project_path(p.namespace, p)
+ url: project_path(p)
}
end
end
@@ -126,6 +126,26 @@ module SearchHelper
search_path(options)
end
+ def search_filter_input_options(type)
+ opts = {
+ id: "filtered-search-#{type}",
+ placeholder: 'Search or filter results...',
+ data: {
+ 'username-params' => @users.to_json(only: [:id, :username])
+ }
+ }
+
+ if @project.present?
+ opts[:data]['project-id'] = @project.id
+ opts[:data]['base-endpoint'] = project_path(@project)
+ else
+ # Group context
+ opts[:data]['base-endpoint'] = group_canonical_path(@group)
+ end
+
+ opts
+ end
+
# Sanitize a HTML field for search display. Most tags are stripped out and the
# maximum length is set to 200 characters.
def search_md_sanitize(object, field)
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 8706876ae4a..1a4f1431bdc 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -45,6 +45,14 @@ module SelectsHelper
end
end
+ with_feature_enabled_data_attribute =
+ case opts.delete(:with_feature_enabled)
+ when 'issues' then 'data-with-issues-enabled'
+ when 'merge_requests' then 'data-with-merge-requests-enabled'
+ end
+
+ opts[with_feature_enabled_data_attribute] = true
+
hidden_field_tag(id, opts[:selected], opts)
end
@@ -67,7 +75,7 @@ module SelectsHelper
current_user: opts[:current_user] || false,
"push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
author_id: opts[:author_id] || '',
- skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil,
+ skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil
}
end
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 979264c9421..b447d4952e7 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,19 +1,26 @@
module SnippetsHelper
def reliable_snippet_path(snippet, opts = nil)
if snippet.project_id?
- namespace_project_snippet_path(snippet.project.namespace,
- snippet.project, snippet, opts)
+ project_snippet_path(snippet.project, snippet, opts)
else
snippet_path(snippet, opts)
end
end
+ def download_snippet_path(snippet)
+ if snippet.project_id
+ raw_project_snippet_path(@project, snippet, inline: false)
+ else
+ raw_snippet_path(snippet, inline: false)
+ end
+ end
+
# Return the path of a snippets index for a user or for a project
#
# @returns String, path to snippet index
def subject_snippets_path(subject = nil, opts = nil)
if subject.is_a?(Project)
- namespace_project_snippets_path(subject.namespace, subject, opts)
+ project_snippets_path(subject, opts)
else # assume subject === User
dashboard_snippets_path(opts)
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 2fda98cae90..b408ec0c6a4 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -58,7 +58,7 @@ module SortingHelper
sort_value_due_date_soon => sort_title_due_date_soon,
sort_value_due_date_later => sort_title_due_date_later,
sort_value_start_date_soon => sort_title_start_date_soon,
- sort_value_start_date_later => sort_title_start_date_later,
+ sort_value_start_date_later => sort_title_start_date_later
}
end
@@ -70,6 +70,14 @@ module SortingHelper
}
end
+ def tags_sort_options_hash
+ {
+ sort_value_name => sort_title_name,
+ sort_value_recently_updated => sort_title_recently_updated,
+ sort_value_oldest_updated => sort_title_oldest_updated
+ }
+ end
+
def sort_title_priority
'Priority'
end
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
new file mode 100644
index 00000000000..544c9efb845
--- /dev/null
+++ b/app/helpers/storage_health_helper.rb
@@ -0,0 +1,37 @@
+module StorageHealthHelper
+ def failing_storage_health_message(storage_health)
+ storage_name = content_tag(:strong, h(storage_health.storage_name))
+ host_names = h(storage_health.failing_on_hosts.to_sentence)
+ translation_params = { storage_name: storage_name,
+ host_names: host_names,
+ failed_attempts: storage_health.total_failures }
+
+ translation = n_('%{storage_name}: failed storage access attempt on host:',
+ '%{storage_name}: %{failed_attempts} failed storage access attempts:',
+ storage_health.total_failures) % translation_params
+
+ translation.html_safe
+ end
+
+ def message_for_circuit_breaker(circuit_breaker)
+ maximum_failures = circuit_breaker.failure_count_threshold
+ current_failures = circuit_breaker.failure_count
+ permanently_broken = circuit_breaker.circuit_broken? && current_failures >= maximum_failures
+
+ translation_params = { number_of_failures: current_failures,
+ maximum_failures: maximum_failures,
+ number_of_seconds: circuit_breaker.failure_wait_time }
+
+ if permanently_broken
+ s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
+ "retry automatically. Reset storage information when the problem is "\
+ "resolved.") % translation_params
+ elsif circuit_breaker.circuit_broken?
+ _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
+ "block access for %{number_of_seconds} seconds.") % translation_params
+ else
+ _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
+ "allow access on the next attempt.") % translation_params
+ end
+ end
+end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index a762b320d56..88f7702db1e 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -1,28 +1,46 @@
module SubmoduleHelper
- include Gitlab::ShellAdapter
+ extend self
+
+ VALID_SUBMODULE_PROTOCOLS = %w[http https git ssh].freeze
# links to files listing for submodule if submodule is a project on this server
def submodule_links(submodule_item, ref = nil, repository = @repository)
url = repository.submodule_url_for(ref, submodule_item.path)
- return url, nil unless url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
-
- namespace = $1
- project = $2
- project.chomp!('.git')
-
- if self_url?(url, namespace, project)
- return namespace_project_path(namespace, project),
- namespace_project_tree_path(namespace, project,
- submodule_item.id)
- elsif relative_self_url?(url)
- relative_self_links(url, submodule_item.id)
- elsif github_dot_com_url?(url)
- standard_links('github.com', namespace, project, submodule_item.id)
- elsif gitlab_dot_com_url?(url)
- standard_links('gitlab.com', namespace, project, submodule_item.id)
+ if url == '.' || url == './'
+ url = File.join(Gitlab.config.gitlab.url, @project.full_path)
+ end
+
+ if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
+ namespace, project = $1, $2
+ gitlab_hosts = [Gitlab.config.gitlab.url,
+ Gitlab.config.gitlab_shell.ssh_path_prefix]
+
+ gitlab_hosts.each do |host|
+ if url.start_with?(host)
+ namespace, _, project = url.sub(host, '').rpartition('/')
+ break
+ end
+ end
+
+ namespace.sub!(/\A\//, '')
+ project.rstrip!
+ project.sub!(/\.git\z/, '')
+
+ if self_url?(url, namespace, project)
+ [namespace_project_path(namespace, project),
+ namespace_project_tree_path(namespace, project, submodule_item.id)]
+ elsif relative_self_url?(url)
+ relative_self_links(url, submodule_item.id)
+ elsif github_dot_com_url?(url)
+ standard_links('github.com', namespace, project, submodule_item.id)
+ elsif gitlab_dot_com_url?(url)
+ standard_links('gitlab.com', namespace, project, submodule_item.id)
+ else
+ [sanitize_submodule_url(url), nil]
+ end
else
- return url, nil
+ [sanitize_submodule_url(url), nil]
end
end
@@ -41,7 +59,7 @@ module SubmoduleHelper
return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/',
project].join('')
url_with_dotgit = url_no_dotgit + '.git'
- url_with_dotgit == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
+ url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join(''))
end
def relative_self_url?(url)
@@ -55,6 +73,7 @@ module SubmoduleHelper
end
def relative_self_links(url, commit)
+ url.rstrip!
# Map relative links to a namespace and project
# For example:
# ../bar.git -> same namespace, repo bar
@@ -73,4 +92,16 @@ module SubmoduleHelper
namespace_project_tree_path(namespace, base, commit)
]
end
+
+ def sanitize_submodule_url(url)
+ uri = URI.parse(url)
+
+ if uri.scheme.in?(VALID_SUBMODULE_PROTOCOLS)
+ uri.to_s
+ else
+ nil
+ end
+ rescue URI::InvalidURIError
+ nil
+ end
end
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index 1ea60e39386..08fd97cd048 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -1,6 +1,7 @@
module SystemNoteHelper
ICON_NAMES_BY_ACTION = {
'commit' => 'icon_commit',
+ 'description' => 'icon_edit',
'merge' => 'icon_merge',
'merged' => 'icon_merged',
'opened' => 'icon_status_open',
@@ -16,7 +17,9 @@ module SystemNoteHelper
'visible' => 'icon_eye',
'milestone' => 'icon_clock_o',
'discussion' => 'icon_comment_o',
- 'moved' => 'icon_arrow_circle_o_right'
+ 'moved' => 'icon_arrow_circle_o_right',
+ 'outdated' => 'icon_edit',
+ 'duplicate' => 'icon_clone'
}.freeze
def icon_for_system_note(note)
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 1a55ee05996..ee701076a14 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -107,8 +107,7 @@ module TabHelper
def branches_tab_class
if current_controller?(:protected_branches) ||
current_controller?(:branches) ||
- current_page?(namespace_project_repository_path(@project.namespace,
- @project))
+ current_page?(project_repository_path(@project))
'active'
end
end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index 31aaf9e5607..d000d6b1c0a 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -10,7 +10,7 @@ module TagsHelper
}
options = exist_opts.merge(options)
- namespace_project_tags_path(@project.namespace, @project, @id, options)
+ project_tags_path(@project, @id, options)
end
def tag_list(project)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 4f5adf623f2..2a7aa299e83 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -4,7 +4,7 @@ module TodosHelper
end
def todos_count_format(count)
- count > 99 ? '99+' : count
+ count > 99 ? '99+' : count.to_s
end
def todos_done_count
@@ -13,21 +13,24 @@ module TodosHelper
def todo_action_name(todo)
case todo.action
- when Todo::ASSIGNED then 'assigned you'
- when Todo::MENTIONED then 'mentioned you on'
+ when Todo::ASSIGNED then todo.self_added? ? 'assigned' : 'assigned you'
+ when Todo::MENTIONED then "mentioned #{todo_action_subject(todo)} on"
when Todo::BUILD_FAILED then 'The build failed for'
when Todo::MARKED then 'added a todo for'
- when Todo::APPROVAL_REQUIRED then 'set you as an approver for'
+ when Todo::APPROVAL_REQUIRED then "set #{todo_action_subject(todo)} as an approver for"
when Todo::UNMERGEABLE then 'Could not merge'
- when Todo::DIRECTLY_ADDRESSED then 'directly addressed you on'
+ when Todo::DIRECTLY_ADDRESSED then "directly addressed #{todo_action_subject(todo)} on"
end
end
def todo_target_link(todo)
- target = todo.target_type.titleize.downcase
- link_to "#{target} #{todo.target_reference}", todo_target_path(todo),
- class: 'has-tooltip',
- title: todo.target.title
+ text = raw("#{todo.target_type.titleize.downcase} ") +
+ if todo.for_commit?
+ content_tag(:span, todo.target_reference, class: 'commit-sha')
+ else
+ todo.target_reference
+ end
+ link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
end
def todo_target_path(todo)
@@ -36,7 +39,7 @@ module TodosHelper
anchor = dom_id(todo.note) if todo.note.present?
if todo.for_commit?
- namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
+ project_commit_path(todo.project,
todo.target, anchor: anchor)
else
path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
@@ -63,7 +66,7 @@ module TodosHelper
project_id: params[:project_id],
author_id: params[:author_id],
type: params[:type],
- action_id: params[:action_id],
+ action_id: params[:action_id]
}
end
@@ -148,6 +151,10 @@ module TodosHelper
private
+ def todo_action_subject(todo)
+ todo.self_added? ? 'yourself' : 'you'
+ end
+
def show_todo_state?(todo)
(todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state)
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index f7b5a5f4dfc..e0d3e9b88f3 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -76,19 +76,19 @@ module TreeHelper
"A new branch will be created in your fork and a new merge request will be started."
end
- def tree_breadcrumbs(tree, max_links = 2)
+ def path_breadcrumbs(max_links = 6)
if @path.present?
part_path = ""
parts = @path.split('/')
- yield('..', nil) if parts.count > max_links
+ yield('..', File.join(*parts.first(parts.count - 2))) if parts.count > max_links
parts.each do |part|
part_path = File.join(part_path, part) unless part_path.empty?
part_path = part if part_path.empty?
next if parts.count > max_links && !parts.last(2).include?(part)
- yield(part, tree_join(@ref, part_path))
+ yield(part, part_path)
end
end
end
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
index a48d4475e97..ce435ca2241 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/triggers_helper.rb
@@ -8,6 +8,6 @@ module TriggersHelper
end
def service_trigger_url(service)
- "#{Settings.gitlab.url}/api/v3/projects/#{service.project_id}/services/#{service.to_param}/trigger"
+ "#{Settings.gitlab.url}/api/v4/projects/#{service.project_id}/services/#{service.to_param}/trigger"
end
end
diff --git a/app/helpers/u2f_helper.rb b/app/helpers/u2f_helper.rb
index 143b4ca6b51..81bfe5d4eeb 100644
--- a/app/helpers/u2f_helper.rb
+++ b/app/helpers/u2f_helper.rb
@@ -1,5 +1,5 @@
module U2fHelper
def inject_u2f_api?
- browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile?
+ ((browser.chrome? && browser.version.to_i >= 41) || (browser.opera? && browser.version.to_i >= 40)) && !browser.device.mobile?
end
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 9c623c9ba7c..b5f54d3e154 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -4,4 +4,14 @@ module UsersHelper
title: user.email,
class: 'has-tooltip commit-committer-link')
end
+
+ def user_email_help_text(user)
+ 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 }
+ end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index b4aaf498068..35755bc149b 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -29,11 +29,11 @@ module VisibilityLevelHelper
def project_visibility_level_description(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
- "Project access must be granted explicitly to each user."
+ _("Project access must be granted explicitly to each user.")
when Gitlab::VisibilityLevel::INTERNAL
- "The project can be cloned by any logged in user."
+ _("The project can be accessed by any logged in user.")
when Gitlab::VisibilityLevel::PUBLIC
- "The project can be cloned without any authentication."
+ _("The project can be accessed without any authentication.")
end
end
@@ -81,7 +81,9 @@ module VisibilityLevelHelper
end
def visibility_level_label(level)
- Project.visibility_levels.key(level)
+ # The visibility level can be:
+ # 'VisibilityLevel|Private', 'VisibilityLevel|Internal', 'VisibilityLevel|Public'
+ s_(Project.visibility_levels.key(level))
end
def restricted_visibility_levels(show_all = false)
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 6bacda9fe75..33453dd178f 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -11,20 +11,31 @@ module WebpackHelper
paths = Webpack::Rails::Manifest.asset_paths(source)
if extension
- paths = paths.select { |p| p.ends_with? ".#{extension}" }
+ paths.select! { |p| p.ends_with? ".#{extension}" }
end
- # include full webpack-dev-server url for rspec tests running locally
+ force_host = webpack_public_host
+ if force_host
+ paths.map! { |p| "#{force_host}#{p}" }
+ end
+
+ paths
+ end
+
+ def webpack_public_host
if Rails.env.test? && Rails.configuration.webpack.dev_server.enabled
host = Rails.configuration.webpack.dev_server.host
port = Rails.configuration.webpack.dev_server.port
protocol = Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
-
- paths.map! do |p|
- "#{protocol}://#{host}:#{port}#{p}"
- end
+ "#{protocol}://#{host}:#{port}"
+ else
+ ActionController::Base.asset_host.try(:chomp, '/')
end
+ end
- paths
+ def webpack_public_path
+ relative_path = Rails.application.config.relative_url_root
+ webpack_path = Rails.application.config.webpack.public_path
+ File.join(webpack_public_host.to_s, relative_path.to_s, webpack_path.to_s, '')
end
end
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 3e3f6246fc5..99212a3438f 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -6,8 +6,8 @@ module WikiHelper
# Returns a String composed of the capitalized name of each directory and the
# capitalized name of the page itself.
def breadcrumb(page_slug)
- page_slug.split('/').
- map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }.
- join(' / ')
+ page_slug.split('/')
+ .map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }
+ .join(' / ')
end
end