diff options
author | Zeger-Jan van de Weg <zegerjan@gitlab.com> | 2016-03-29 17:26:59 +0200 |
---|---|---|
committer | Zeger-Jan van de Weg <zegerjan@gitlab.com> | 2016-03-29 17:26:59 +0200 |
commit | 3339513ca67c50a231b8906a33eccc0d209270a5 (patch) | |
tree | e9dbe77b814abd59062ba5dcdd3f454011d758cc /app/helpers | |
parent | 15a6633999c81387245cabf129dd2fbb04650c95 (diff) | |
parent | 54957d6932c2b159e01b60ee1d4e191cfdf5b713 (diff) | |
download | gitlab-ce-3339513ca67c50a231b8906a33eccc0d209270a5.tar.gz |
Merge branch 'master' into assign-to-issuable-opener
Diffstat (limited to 'app/helpers')
26 files changed, 444 insertions, 141 deletions
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index c5820bf4c50..e0abc3a2869 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -1,21 +1,33 @@ module AppearancesHelper - def brand_item - nil - end - def brand_title - 'GitLab Community Edition' + if brand_item && brand_item.title + brand_item.title + else + 'GitLab Community Edition' + end end def brand_image - nil + if brand_item.logo? + image_tag brand_item.logo + else + nil + end end def brand_text - nil + markdown(brand_item.description) + end + + def brand_item + @appearance ||= Appearance.first end def brand_header_logo - render 'shared/logo.svg' + if brand_item && brand_item.header_logo? + image_tag brand_item.header_logo + else + render 'shared/logo.svg' + end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f0aa2b57121..e6ceb213532 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -72,7 +72,7 @@ module ApplicationHelper if user_or_email.is_a?(User) user = user_or_email else - user = User.find_by(email: user_or_email.downcase) + user = User.find_by_any_email(user_or_email.try(:downcase)) end if user @@ -182,7 +182,7 @@ module ApplicationHelper # Returns an HTML-safe String def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) element = content_tag :time, time.to_s, - class: "#{html_class} js-timeago js-timeago-pending", + class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}", datetime: time.to_time.getutc.iso8601, title: time.in_time_zone.to_s(:medium), data: { toggle: 'tooltip', placement: placement, container: 'body' } @@ -196,6 +196,22 @@ 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 + + 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) + + 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) + end + + output + end + end + def render_markup(file_name, file_content) if gitlab_markdown?(file_name) Haml::Helpers.preserve(markdown(file_content)) @@ -285,7 +301,7 @@ module ApplicationHelper if project.nil? nil elsif current_controller?(:issues) - project.issues.send(entity).count + project.issues.visible_to_user(current_user).send(entity).count elsif current_controller?(:merge_requests) project.merge_requests.send(entity).count end diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index de669e529a7..b4f80fd9b3e 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -6,6 +6,10 @@ module AuthHelper Gitlab.config.ldap.enabled end + def omniauth_enabled? + Gitlab.config.omniauth.enabled + end + def provider_has_icon?(name) PROVIDERS_WITH_ICONS.include?(name.to_s) end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 7143a744869..820d69c230b 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -27,7 +27,7 @@ module BlobHelper link_opts) if !on_top_of_branch?(project, ref) - button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } + button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } elsif can_edit_blob?(blob, project, ref) link_to "Edit", edit_path, class: 'btn' elsif can?(current_user, :fork_project, project) @@ -50,9 +50,9 @@ module BlobHelper return unless blob if !on_top_of_branch?(project, ref) - button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } + button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } elsif blob.lfs_pointer? - button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } + button_tag label, class: "btn btn-#{btn_class} 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_edit_blob?(blob, project, ref) button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' elsif can?(current_user, :fork_project, project) @@ -134,4 +134,43 @@ module BlobHelper blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml blob end + + # If we blindly set the 'real' content type when serving a Git blob we + # are enabling XSS attacks. An attacker could upload e.g. a Javascript + # file to a Git repository, trick the browser of a victim into + # downloading the blob, and then the 'application/javascript' content + # type would tell the browser to execute the attacker's Javascript. By + # overriding the content type and setting it to 'text/plain' (in the + # example of Javascript) we tell the browser of the victim not to + # execute untrusted data. + def safe_content_type(blob) + if blob.text? + 'text/plain; charset=utf-8' + elsif blob.image? + blob.content_type + else + 'application/octet-stream' + end + end + + def cached_blob? + stale = stale?(etag: @blob.id) # The #stale? method sets cache headers. + + # Because we are opionated we set the cache headers ourselves. + response.cache_control[:public] = @project.public? + + if @ref && @commit && @ref == @commit.id + # This is a link to a commit by its commit SHA. That means that the blob + # is immutable. The only reason to invalidate the cache is if the commit + # was deleted or if the user lost access to the repository. + response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE + else + # A branch or tag points at this blob. That means that the expected blob + # value may change over time. + response.cache_control[:max_age] = Blob::CACHE_TIME + end + + response.etag = @blob.id + !stale + end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index d6c05843743..a9047ede8c5 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -23,36 +23,34 @@ module ButtonHelper end def http_clone_button(project) - klass = 'btn js-protocol-switch' - klass << ' active' if default_clone_protocol == 'http' - klass << ' has_tooltip' if current_user.try(:require_password?) + klass = 'http-selector' + klass << ' has-tooltip' if current_user.try(:require_password?) protocol = gitlab_config.protocol.upcase - content_tag :button, protocol, + content_tag :a, protocol, class: klass, + href: @project.http_url_to_repo, data: { - clone: project.http_url_to_repo, + html: true, + placement: 'right', container: 'body', - html: 'true', title: "Set a password on your account<br>to pull or push via #{protocol}" - }, - type: :button + } end def ssh_clone_button(project) - klass = 'btn js-protocol-switch' - klass << ' active' if default_clone_protocol == 'ssh' - klass << ' has_tooltip' if current_user.try(:require_ssh_key?) + klass = 'ssh-selector' + klass << ' has-tooltip' if current_user.try(:require_ssh_key?) - content_tag :button, 'SSH', + content_tag :a, 'SSH', class: klass, + href: project.ssh_url_to_repo, data: { - clone: project.ssh_url_to_repo, + html: true, + placement: 'right', container: 'body', - html: 'true', title: 'Add an SSH key to your profile<br>to pull or push via SSH.' - }, - type: :button + } end end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index d8bee21c82e..8b1575d5e0c 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -12,9 +12,13 @@ module CiStatusHelper ci_label_for_status(ci_commit.status) end - def ci_status_with_icon(status) - content_tag :span, class: "ci-status ci-#{status}" do - ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status) + def ci_status_with_icon(status, target = nil) + content = ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status) + klass = "ci-status ci-#{status}" + if target + link_to content, target, class: klass + else + content_tag :span, content, class: klass end end @@ -42,12 +46,12 @@ module CiStatusHelper icon(icon_name + ' fw') end - def render_ci_status(ci_commit) + def render_ci_status(ci_commit, tooltip_placement: 'auto left') link_to ci_status_icon(ci_commit), ci_status_path(ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", title: "Build #{ci_status_label(ci_commit)}", - data: { toggle: 'tooltip', placement: 'left' } + data: { toggle: 'tooltip', placement: tooltip_placement } end def no_runners_for_project?(project) diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 7ff539118d3..bde0799f3de 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -130,7 +130,7 @@ module CommitsHelper if can_collaborate_with_project? content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do - link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}" + link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}" end elsif can?(current_user, :fork_project, @project) continue_params = { @@ -142,7 +142,7 @@ module CommitsHelper namespace_key: current_user.namespace.id, continue: continue_params) - link_to 'Revert', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', title: tooltip + link_to 'Revert', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip end end @@ -182,7 +182,7 @@ module CommitsHelper end options = { - class: "commit-#{options[:source]}-link has_tooltip", + class: "commit-#{options[:source]}-link has-tooltip", data: { 'original-title'.to_sym => sanitize(source_email) } } @@ -211,4 +211,15 @@ module CommitsHelper def clean(string) Sanitize.clean(string, remove_contents: true) end + + def limited_commits(commits) + if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + [ + commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), + commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE + ] + else + [commits, 0] + end + end end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 6a3ab3ea40a..ff32e834499 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -12,40 +12,20 @@ module DiffHelper params[:view] == 'parallel' ? 'parallel' : 'inline' end - def allowed_diff_size - if diff_hard_limit_enabled? - Commit::DIFF_HARD_LIMIT_FILES - else - Commit::DIFF_SAFE_FILES - end + def diff_hard_limit_enabled? + params[:force_show_diff].present? end - def allowed_diff_lines + def diff_options + options = { ignore_whitespace_change: params[:w] == '1' } if diff_hard_limit_enabled? - Commit::DIFF_HARD_LIMIT_LINES - else - Commit::DIFF_SAFE_LINES + options.merge!(Commit.max_diff_options) end + options end def safe_diff_files(diffs, diff_refs) - lines = 0 - safe_files = [] - diffs.first(allowed_diff_size).each do |diff| - lines += diff.diff.lines.count - break if lines > allowed_diff_lines - safe_files << Gitlab::Diff::File.new(diff, diff_refs) - end - safe_files - end - - def diff_hard_limit_enabled? - # Enabling hard limit allows user to see more diff information - if params[:force_show_diff].present? - true - else - false - end + diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) } end def generate_line_code(file_path, line) @@ -137,7 +117,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "#{name}-diff-btn", class: (selected ? 'btn active' : 'btn') do + link_to url_for(params_copy), id: "#{name}-diff-btn", class: (selected ? 'btn active' : 'btn'), data: { view_type: name } do title end end diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb new file mode 100644 index 00000000000..14697f774cc --- /dev/null +++ b/app/helpers/dropdowns_helper.rb @@ -0,0 +1,101 @@ +module DropdownsHelper + def dropdown_tag(toggle_text, options: {}, &block) + content_tag :div, class: "dropdown" do + data_attr = { toggle: "dropdown" } + + if options.has_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 + output = "" + + if options.has_key?(:title) + output << dropdown_title(options[:title]) + end + + if options.has_key?(:filter) + output << dropdown_filter(options[:placeholder]) + end + + output << content_tag(:div, class: "dropdown-content") do + capture(&block) if block && !options.has_key?(:footer_content) + end + + if block && options[:footer_content] + output << content_tag(:div, class: "dropdown-footer") do + capture(&block) + end + end + + output << dropdown_loading + + output.html_safe + end + + dropdown_output.html_safe + end + end + + def dropdown_toggle(toggle_text, data_attr, options) + 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 + output = content_tag(:span, toggle_text, class: "dropdown-toggle-text") + output << icon('chevron-down') + output.html_safe + end + end + + def dropdown_title(title, back: false) + content_tag :div, class: "dropdown-title" do + title_output = "" + + if back + title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back", aria: { label: "Go back" }, type: "button") do + icon('arrow-left') + end + end + + 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') + end + + title_output.html_safe + end + end + + def dropdown_filter(placeholder) + content_tag :div, class: "dropdown-input" do + filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder + filter_output << icon('search', class: "dropdown-input-search") + filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button") + + filter_output.html_safe + end + end + + def dropdown_content(&block) + content_tag(:div, class: "dropdown-content") do + if block + capture(&block) + end + end + end + + def dropdown_footer(&block) + content_tag(:div, class: "dropdown-footer") do + if block + capture(&block) + end + end + end + + def dropdown_loading + content_tag :div, class: "dropdown-loading" do + icon('spinner spin') + end + end +end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 31bf45baeb7..d3e5e3aa8b9 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -3,7 +3,7 @@ module EventsHelper author = event.author if author - link_to author.name, user_path(author.username) + link_to author.name, user_path(author.username), title: h(author.name) else event.author_name end @@ -159,7 +159,7 @@ module EventsHelper link_to( namespace_project_commit_path(event.project.namespace, event.project, event.note_commit_id, - anchor: dom_id(event.target)), + anchor: dom_id(event.target), title: h(event.target_title)), class: "commit_short_id" ) do "#{event.note_target_type} #{event.note_short_commit_id}" @@ -167,12 +167,12 @@ module EventsHelper elsif event.note_project_snippet? link_to(namespace_project_snippet_path(event.project.namespace, event.project, - event.note_target)) do - "#{event.note_target_type} ##{truncate event.note_target_id}" + event.note_target), title: h(event.project.name)) do + "#{event.note_target_type} #{truncate event.note_target.to_reference}" end else link_to event_note_target_path(event) do - "#{event.note_target_type} ##{truncate event.note_target_iid}" + "#{event.note_target_type} #{truncate event.note_target.to_reference}" end end else @@ -194,7 +194,7 @@ module EventsHelper end def event_to_atom(xml, event) - if event.proper? + if event.visible_to_user?(current_user) xml.entry do event_link = event_feed_url(event) event_title = event_feed_title(event) diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb index 3648757428b..337b0aacbb5 100644 --- a/app/helpers/explore_helper.rb +++ b/app/helpers/explore_helper.rb @@ -1,5 +1,5 @@ module ExploreHelper - def explore_projects_filter_path(options={}) + def filter_projects_path(options={}) exist_opts = { sort: params[:sort], scope: params[:scope], @@ -9,15 +9,7 @@ module ExploreHelper } options = exist_opts.merge(options) - - path = if explore_controller? - explore_projects_path - elsif current_action?(:starred) - starred_dashboard_projects_path - else - dashboard_projects_path - end - + path = request.path path << "?#{options.to_param}" path end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 89d2a648494..2f760af02fd 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -50,6 +50,8 @@ module GitlabMarkdownHelper context[:project] ||= @project + text = Banzai.pre_process(text, context) + html = Banzai.render(text, context) context.merge!( diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 1d36969cd62..b1f0a765bb9 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -19,6 +19,10 @@ module GroupsHelper end end + def can_change_group_visibility_level?(group) + can?(current_user, :change_visibility_level, group) + end + def group_icon(group) if group.is_a?(String) group = Group.find_by(path: group) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 84c6d0883b0..ab3ef454e1c 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -10,6 +10,15 @@ module IconsHelper options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options) end + def audit_icon(names, options = {}) + case names + when "standard" + names = "key" + end + + options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options) + end + def spinner(text = nil, visible = false) css_class = 'loading' css_class << ' hide' unless visible @@ -37,7 +46,7 @@ module IconsHelper else # Gitlab::VisibilityLevel::PUBLIC 'globe' end - + name << " fw" if fw icon(name) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 91a3aa371ef..62050691a39 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -16,10 +16,37 @@ module IssuablesHelper base_issuable_scope(issuable).where('iid > ?', issuable.iid).last end + def issuable_json_path(issuable) + project = issuable.project + + if issuable.kind_of?(MergeRequest) + namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json) + else + namespace_project_issue_path(project.namespace, project, issuable.iid, :json) + end + end + def prev_issuable_for(issuable) base_issuable_scope(issuable).where('iid < ?', issuable.iid).first end + def user_dropdown_label(user_id, default_label) + return "Unassigned" if user_id == "0" + + if @project + member = @project.team.find_member(user_id) + user = member.user if member + else + user = User.find_by(id: user_id) + end + + if user + user.name + else + default_label + end + end + private def sidebar_gutter_collapsed? @@ -31,7 +58,11 @@ module IssuablesHelper end def issuable_state_scope(issuable) - issuable.open? ? :opened : :closed + if issuable.respond_to?(:merged?) && issuable.merged? + :merged + else + issuable.open? ? :opened : :closed + end end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index ae4ebc0854a..24b90fef4fe 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -57,6 +57,19 @@ module IssuesHelper options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id) end + def project_options(issuable, current_user, ability: :read_project) + projects = current_user.authorized_projects + projects = projects.select do |project| + current_user.can?(ability, project) + end + + no_project = OpenStruct.new(id: 0, name_with_namespace: 'No project') + projects.unshift(no_project) + projects.delete(issuable.project) + + options_from_collection_for_select(projects, :id, :name_with_namespace) + end + def status_box_class(item) if item.respond_to?(:expired?) && item.expired? 'status-box-expired' @@ -98,6 +111,10 @@ module IssuesHelper end.sort.to_sentence(last_word_connector: ', or ') end + def confidential_icon(issue) + icon('eye-slash') if issue.confidential? + end + def emoji_icon(name, unicode = nil, aliases = []) unicode ||= Emoji.emoji_filename(name) rescue "" diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 1c7fcc13b42..3dded7c2f23 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -32,7 +32,7 @@ module LabelsHelper # link_to_label(label) { "My Custom Label Text" } # # Returns a String - def link_to_label(label, project: nil, type: :issue, &block) + def link_to_label(label, project: nil, type: :issue, tooltip: true, &block) project ||= @project || label.project link = send("namespace_project_#{type.to_s.pluralize}_path", project.namespace, @@ -42,7 +42,7 @@ module LabelsHelper if block_given? link_to link, &block else - link_to render_colored_label(label), link + link_to render_colored_label(label, tooltip: tooltip), link end end @@ -50,19 +50,26 @@ module LabelsHelper @project.labels.pluck(:title) end - def render_colored_label(label) + def render_colored_label(label, label_suffix = '', tooltip: true) label_color = label.color || Label::DEFAULT_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") + - %( style="background-color: #{label_color}; color: #{text_color}">) + - escape_once(label.name) + '</span>' + span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) + + %(style="background-color: #{label_color}; color: #{text_color}" ) + + %(title="#{escape_once(label.description)}" data-container="body">) + + %(#{escape_once(label.name)}#{label_suffix}</span>) span.html_safe end + def render_colored_cross_project_label(label, tooltip: true) + label_suffix = label.project.name_with_namespace + label_suffix = " <i>in #{escape_once(label_suffix)}</i>" + render_colored_label(label, label_suffix, tooltip: tooltip) + end + def suggested_colors [ '#0033CC', @@ -103,21 +110,23 @@ module LabelsHelper end end - def projects_labels_options - labels = - if @project - @project.labels - else - Label.where(project_id: @projects) - end + def labels_filter_path + if @project + namespace_project_labels_path(@project.namespace, @project, :json) + else + dashboard_labels_path(:json) + end + end - grouped_labels = GlobalLabel.build_collection(labels) - grouped_labels.unshift(Label::None) - grouped_labels.unshift(Label::Any) + def label_subscription_status(label) + label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed' + end - options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name]) + def label_subscription_toggle_button_text(label) + label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' end # Required for Banzai::Filter::LabelReferenceFilter - module_function :render_colored_label, :text_color_for_bg, :escape_once + module_function :render_colored_label, :render_colored_cross_project_label, + :text_color_for_bg, :escape_once end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index a42cbcff182..87fc2db6901 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -9,10 +9,36 @@ module MilestonesHelper end end + def milestones_label_path(opts = {}) + if @project + namespace_project_issues_path(@project.namespace, @project, opts) + elsif @group + issues_group_path(@group, opts) + else + issues_dashboard_path(opts) + end + end + + def milestones_browse_issuables_path(milestone, type:) + opts = { milestone_title: milestone.title } + + if @project + polymorphic_path([@project.namespace.becomes(Namespace), @project, type], opts) + elsif @group + polymorphic_url([type, @group], opts) + else + polymorphic_url([type, :dashboard], opts) + end + end + + def milestone_issues_by_label_count(milestone, label, state:) + milestone.issues.with_label(label.title).send(state).size + end + def milestone_progress_bar(milestone) options = { class: 'progress-bar progress-bar-success', - style: "width: #{milestone.percent_complete}%;" + style: "width: #{milestone.percent_complete(current_user)}%;" } content_tag :div, class: 'progress' do @@ -20,20 +46,21 @@ module MilestonesHelper end end - def projects_milestones_options - milestones = - if @project - @project.milestones - else - Milestone.where(project_id: @projects) - end.active - - epoch = DateTime.parse('1970-01-01') - grouped_milestones = GlobalMilestone.build_collection(milestones) - grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } - grouped_milestones.unshift(Milestone::None) - grouped_milestones.unshift(Milestone::Any) - - options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title]) + def milestones_filter_dropdown_path + if @project + namespace_project_milestones_path(@project.namespace, @project, :json) + else + dashboard_milestones_path(:json) + end + end + + def milestone_remaining_days(milestone) + if milestone.expired? + content_tag(:strong, 'expired') + elsif milestone.due_date + days = milestone.remaining_days + content = content_tag(:strong, days) + content << " #{'day'.pluralize(days)} remaining" + end end end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 29cb753e62c..5d86bd490a8 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -23,6 +23,7 @@ module NavHelper if current_path?('merge_requests#show') || current_path?('merge_requests#diffs') || current_path?('merge_requests#commits') || + current_path?('merge_requests#builds') || current_path?('issues#show') if cookies[:collapsed_gutter] == 'true' "page-gutter right-sidebar-collapsed" diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index c73cb3028ee..c3832cf5d65 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -12,7 +12,9 @@ module PreferencesHelper projects: 'Your Projects (default)', stars: 'Starred Projects', project_activity: "Your Projects' Activity", - starred_project_activity: "Starred Projects' Activity" + starred_project_activity: "Starred Projects' Activity", + groups: "Your Groups", + todos: "Your Todos" }.with_indifferent_access.freeze # Returns an Array usable by a select field for more user-friendly option text diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d6fb629b0c2..4e4c6e301d5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -8,7 +8,7 @@ module ProjectsHelper end def link_to_project(project) - link_to [project.namespace.becomes(Namespace), project] do + link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do title = content_tag(:span, project.name, class: 'project-name') if project.namespace @@ -26,7 +26,7 @@ module ProjectsHelper image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] end - def link_to_member(project, author, opts = {}) + def link_to_member(project, author, opts = {}, &block) default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } opts = default_opts.merge(opts) @@ -38,15 +38,21 @@ module ProjectsHelper author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] # Build name span tag - author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] + if opts[:by_username] + author_html << content_tag(:span, sanitize("@#{author.username}"), class: opts[:author_class]) if opts[:name] + else + author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] + end + + author_html << capture(&block) if block author_html = author_html.html_safe if opts[:name] - link_to(author_html, user_path(author), class: "author_link").html_safe + link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe else title = opts[:title].sub(":name", sanitize(author.name)) - link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe + link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe end end @@ -203,7 +209,7 @@ module ProjectsHelper def default_clone_protocol if !current_user || current_user.require_ssh_key? - "http" + gitlab_config.protocol else "ssh" end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 1eb790b1796..494dad0b41e 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -40,7 +40,7 @@ module SearchHelper { label: "help: Rake Tasks Help", url: help_page_path("raketasks", "README") }, { label: "help: SSH Keys Help", url: help_page_path("ssh", "README") }, { label: "help: System Hooks Help", url: help_page_path("system_hooks", "system_hooks") }, - { label: "help: Web Hooks Help", url: help_page_path("web_hooks", "web_hooks") }, + { label: "help: Webhooks Help", url: help_page_path("web_hooks", "web_hooks") }, { label: "help: Workflow Help", url: help_page_path("workflow", "README") }, ] end diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 41ae4048992..0a5a8eb5aee 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -1,14 +1,4 @@ module SnippetsHelper - def lifetime_select_options - options = [ - ['forever', nil], - ['1 day', "#{Date.current + 1.day}"], - ['1 week', "#{Date.current + 1.week}"], - ['1 month', "#{Date.current + 1.month}"] - ] - options_for_select(options) - end - def reliable_snippet_path(snippet) if snippet.project_id? namespace_project_snippet_path(snippet.project.namespace, diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index f9026b887da..2f2d2721d6d 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -16,6 +16,16 @@ module SortingHelper } end + def projects_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, + sort_value_recently_created => sort_title_recently_created, + sort_value_oldest_created => sort_title_oldest_created, + } + end + def sort_title_oldest_updated 'Oldest updated' end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 4b745a5b969..edc5686cf08 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -16,14 +16,19 @@ module TodosHelper def todo_target_link(todo) target = todo.target_type.titleize.downcase - link_to "#{target} #{todo.target.to_reference}", todo_target_path(todo) + link_to "#{target} #{todo.target_reference}", todo_target_path(todo), { title: todo.target.title } end def todo_target_path(todo) anchor = dom_id(todo.note) if todo.note.present? - polymorphic_path([todo.project.namespace.becomes(Namespace), - todo.project, todo.target], anchor: anchor) + if todo.for_commit? + namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project, + todo.target, anchor: anchor) + else + polymorphic_path([todo.project.namespace.becomes(Namespace), + todo.project, todo.target], anchor: anchor) + end end def todos_filter_params diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 71d33b445c2..3a83ae15dd8 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -19,6 +19,8 @@ module VisibilityLevelHelper case form_model when Project project_visibility_level_description(level) + when Group + group_visibility_level_description(level) when Snippet snippet_visibility_level_description(level, form_model) end @@ -35,6 +37,17 @@ module VisibilityLevelHelper end end + def group_visibility_level_description(level) + case level + when Gitlab::VisibilityLevel::PRIVATE + "The group and its projects can only be viewed by members." + when Gitlab::VisibilityLevel::INTERNAL + "The group and any internal projects can be viewed by any logged in user." + when Gitlab::VisibilityLevel::PUBLIC + "The group and any public projects can be viewed without any authentication." + end + end + def snippet_visibility_level_description(level, snippet = nil) case level when Gitlab::VisibilityLevel::PRIVATE @@ -50,6 +63,23 @@ module VisibilityLevelHelper end end + def visibility_icon_description(form_model) + case form_model + when Project + project_visibility_icon_description(form_model.visibility_level) + when Group + group_visibility_icon_description(form_model.visibility_level) + end + end + + def group_visibility_icon_description(level) + "#{visibility_level_label(level)} - #{group_visibility_level_description(level)}" + end + + def project_visibility_icon_description(level) + "#{visibility_level_label(level)} - #{project_visibility_level_description(level)}" + end + def visibility_level_label(level) Project.visibility_levels.key(level) end @@ -67,8 +97,11 @@ module VisibilityLevelHelper current_application_settings.default_snippet_visibility end + def default_group_visibility + current_application_settings.default_group_visibility + end + def skip_level?(form_model, level) - form_model.is_a?(Project) && - !form_model.visibility_level_allowed?(level) + form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level) end end |