diff options
author | Baldinof <baldinof@gmail.com> | 2016-03-09 22:54:53 +0100 |
---|---|---|
committer | Baldinof <baldinof@gmail.com> | 2016-03-09 22:54:53 +0100 |
commit | a5079d219548c4e94eb78953811158c346225174 (patch) | |
tree | cf122ee677415a6891c60c244766680bdc3e288d /app | |
parent | a603422edb69248c822dd6c4a9d7860194d89a3d (diff) | |
parent | d9042e8b399608fa189ffdb02efa8de30604088d (diff) | |
download | gitlab-ce-a5079d219548c4e94eb78953811158c346225174.tar.gz |
Merge branch 'master' into markdown_preview_shortcut
Diffstat (limited to 'app')
83 files changed, 658 insertions, 718 deletions
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index d7feb5d5c87..54b28f2dd8d 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -23,7 +23,7 @@ class Dispatcher new Issue() shortcut_handler = new ShortcutsIssuable() new ZenMode() - when 'projects:milestones:show' + when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show' new Milestone() when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee index e6d8518bec8..0037a3a21c2 100644 --- a/app/assets/javascripts/milestone.js.coffee +++ b/app/assets/javascripts/milestone.js.coffee @@ -69,7 +69,7 @@ class @Milestone @bindIssuesSorting() @bindMergeRequestSorting() - @bindTabsSwitching + @bindTabsSwitching() bindIssuesSorting: -> $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable( @@ -104,7 +104,7 @@ class @Milestone ).disableSelection() - bindMergeRequestSorting: -> + bindTabsSwitching: -> $('a[data-toggle="tab"]').on 'show.bs.tab', (e) -> currentTabClass = $(e.target).data('show') previousTabClass = $(e.relatedTarget).data('show') @@ -112,7 +112,8 @@ class @Milestone $(previousTabClass).hide() $(currentTabClass).removeClass('hidden') $(currentTabClass).show() - + + bindMergeRequestSorting: -> $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable( connectWith: ".merge_requests-sortable-list", dropOnEmpty: true, diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index d4878b333f9..3dc524ccca4 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -81,8 +81,9 @@ &::before { content: "\f00c"; position: absolute; - left: 4px; - top: 8px; + left: 5px; + top: 50%; + margin-top: -7px; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; @@ -94,8 +95,8 @@ } .dropdown-header { - padding-left: 10px; - padding-right: 10px; + padding-left: 5px; + padding-right: 5px; color: $dropdown-header-color; font-size: 13px; line-height: 22px; diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index d24adbf67e6..d0e72a4422c 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -19,10 +19,11 @@ li.milestone { width: 105px; } - .issue-row { + .issuable-row { .color-label { border-radius: 2px; padding: 3px !important; + margin-right: 7px; } // Issue title @@ -44,20 +45,15 @@ li.milestone { } } -.issues-sortable-list { - .issue-detail { +.issues-sortable-list, .merge_requests-sortable-list { + .issuable-detail { display: block; + margin-top: 7px; - .issue-number{ + .issuable-number { color: rgba(0,0,0,0.44); margin-right: 5px; } - .color-label { - padding: 6px 10px; - margin-right: 7px; - margin-top: 10px; - } - .avatar { float: none; } diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 0161642d871..7d414ae003d 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -26,5 +26,5 @@ margin-right: 10px; font-size: $gl-font-size; border: 1px solid; - line-height: 40px; + line-height: 32px; } diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 4d3e48f7f81..668396a0f20 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -55,7 +55,7 @@ class Admin::GroupsController < Admin::ApplicationController private def group - @group = Group.find_by(path: params[:id]) + @group ||= Group.find_by(path: params[:id]) end def group_params diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb new file mode 100644 index 00000000000..f63b703d101 --- /dev/null +++ b/app/controllers/concerns/filter_projects.rb @@ -0,0 +1,15 @@ +# == FilterProjects +# +# Controller concern to handle projects filtering +# * by name +# * by archived state +# +module FilterProjects + extend ActiveSupport::Concern + + def filter_projects(projects) + projects = projects.search(params[:filter_projects]) if params[:filter_projects].present? + projects = projects.non_archived if params[:archived].blank? + projects + end +end diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index dc880b634e5..fc51c3241af 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -1,18 +1,15 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController + include FilterProjects + before_action :event_filter def index - @projects = current_user.authorized_projects.sorted_by_activity.non_archived - @projects = @projects.sort(@sort = params[:sort]) + @projects = current_user.authorized_projects.sorted_by_activity + @projects = filter_projects(@projects) @projects = @projects.includes(:namespace) + @projects = @projects.sort(@sort = params[:sort]) + @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? - terms = params[:filter_projects] - - if terms.present? - @projects = @projects.search(terms) - end - - @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank? @last_push = current_user.recent_push respond_to do |format| @@ -32,16 +29,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController def starred @projects = current_user.starred_projects.sorted_by_activity + @projects = filter_projects(@projects) @projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.sort(@sort = params[:sort]) + @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? - terms = params[:filter_projects] - - if terms.present? - @projects = @projects.search(terms) - end - - @projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank? @last_push = current_user.recent_push @groups = [] diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index a384f3004db..5b811db3068 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,12 +1,12 @@ class Explore::ProjectsController < Explore::ApplicationController + include FilterProjects + def index @projects = ProjectsFinder.new.execute(current_user) @tags = @projects.tags_on(:tags) @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? - @projects = @projects.non_archived - @projects = @projects.search(params[:search]) if params[:search].present? - @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present? + @projects = filter_projects(@projects) @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? @@ -22,8 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController def trending @projects = TrendingProjectsFinder.new.execute(current_user) - @projects = @projects.non_archived - @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present? + @projects = filter_projects(@projects) @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? respond_to do |format| @@ -38,7 +37,7 @@ class Explore::ProjectsController < Explore::ApplicationController def starred @projects = ProjectsFinder.new.execute(current_user) - @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present? + @projects = filter_projects(@projects) @projects = @projects.reorder('star_count DESC') @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index ca5ce1e2046..f05c29e9974 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,4 +1,5 @@ class GroupsController < Groups::ApplicationController + include FilterProjects include IssuesAction include MergeRequestsAction @@ -41,7 +42,8 @@ class GroupsController < Groups::ApplicationController def show @last_push = current_user.recent_push if current_user @projects = @projects.includes(:namespace) - @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present? + @projects = filter_projects(@projects) + @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? respond_to do |format| @@ -98,7 +100,7 @@ class GroupsController < Groups::ApplicationController end def load_projects - @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived + @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity end # Dont allow unauthorized access to group diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index b64dbbd89ce..a6bebc46b06 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -7,6 +7,9 @@ class Projects::AvatarsController < Projects::ApplicationController @blob = @repository.blob_at_branch('master', @project.avatar_in_git) if @blob headers['X-Content-Type-Options'] = 'nosniff' + + return if cached_blob? + headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) headers['Content-Disposition'] = 'inline' headers['Content-Type'] = safe_content_type(@blob) diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 21f30f278c8..da46731d945 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController end def show - @issues = @milestone.issues - @users = @milestone.participants.uniq - @merge_requests = @milestone.merge_requests - @labels = @milestone.labels end def create diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index d9723acb1d9..10de0e60530 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -13,6 +13,8 @@ class Projects::RawController < Projects::ApplicationController if @blob headers['X-Content-Type-Options'] = 'nosniff' + return if cached_blob? + if @blob.lfs_pointer? send_lfs_object else diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f7240edd618..c88a420b412 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -263,11 +263,9 @@ class IssuableFinder def by_label(items) if labels? if filter_by_no_label? - items = items. - joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id"). - where(label_links: { id: nil }) + items = items.without_label else - items = items.joins(:labels).where(labels: { title: label_names }) + items = items.with_label(label_names) if projects items = items.where(labels: { project_id: projects }) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 07b5759443b..a41172816b8 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -4,7 +4,7 @@ class SnippetsFinder case filter when :all then - snippets(current_user).fresh.non_expired + snippets(current_user).fresh when :by_user then by_user(current_user, params[:user], params[:scope]) when :by_project @@ -27,7 +27,7 @@ class SnippetsFinder end def by_user(current_user, user, scope) - snippets = user.snippets.fresh.non_expired + snippets = user.snippets.fresh return snippets.are_public unless current_user @@ -48,7 +48,7 @@ class SnippetsFinder end def by_project(current_user, project) - snippets = project.snippets.fresh.non_expired + snippets = project.snippets.fresh if current_user if project.team.member?(current_user.id) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f0aa2b57121..368969c6472 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(email: user_or_email.try(:downcase)) end if user diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 7f63a2e2cb4..0f77b3b299a 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -152,4 +152,25 @@ module BlobHelper '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/commits_helper.rb b/app/helpers/commits_helper.rb index a09e91578b6..f994c9e6170 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -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/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/labels_helper.rb b/app/helpers/labels_helper.rb index 1c7fcc13b42..89a054289e8 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -50,19 +50,25 @@ module LabelsHelper @project.labels.pluck(:title) end - def render_colored_label(label) + def render_colored_label(label, label_suffix = '') 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>' + %(style="background-color: #{label_color}; color: #{text_color}">) + + %(#{escape_once(label.name)}#{label_suffix}</span>) span.html_safe end + def render_colored_cross_project_label(label) + label_suffix = label.project.name_with_namespace + label_suffix = " <i>in #{escape_once(label_suffix)}</i>" + render_colored_label(label, label_suffix) + end + def suggested_colors [ '#0033CC', @@ -119,5 +125,6 @@ module LabelsHelper 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 7de81d8dfdb..e3e7daa49c5 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -9,6 +9,32 @@ 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', 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/models/ability.rb b/app/models/ability.rb index f34554d557c..fe9e0aab717 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -9,6 +9,7 @@ class Ability when CommitStatus then commit_status_abilities(user, subject) when Project then project_abilities(user, subject) when Issue then issue_abilities(user, subject) + when ExternalIssue then external_issue_abilities(user, subject) when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject) @@ -424,6 +425,10 @@ class Ability end end + def external_issue_abilities(user, subject) + project_abilities(user, subject.project) + end + private def named_abilities(name) diff --git a/app/models/blob.rb b/app/models/blob.rb index 8ee9f3006b2..72e6c5fa3fd 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -1,5 +1,8 @@ # Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects class Blob < SimpleDelegator + CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute + CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour + # Wrap a Gitlab::Git::Blob object, or return nil when given nil # # This method prevents the decorated object from evaluating to "truthy" when diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 286d6655861..27b97944e38 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -29,12 +29,15 @@ module Issuable scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :unassigned, -> { where("assignee_id IS NULL") } scope :of_projects, ->(ids) { where(project_id: ids) } + scope :of_milestones, ->(ids) { where(milestone_id: ids) } scope :opened, -> { with_state(:opened, :reopened) } scope :only_opened, -> { with_state(:opened) } scope :only_reopened, -> { with_state(:reopened) } scope :closed, -> { with_state(:closed) } scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } + scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) } + scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :join_project, -> { joins(:project) } scope :references_project, -> { references(:project) } diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb new file mode 100644 index 00000000000..d67df7c1d9c --- /dev/null +++ b/app/models/concerns/milestoneish.rb @@ -0,0 +1,25 @@ +module Milestoneish + def closed_items_count + issues.closed.size + merge_requests.closed_and_merged.size + end + + def total_items_count + issues.size + merge_requests.size + end + + def complete? + total_items_count == closed_items_count + end + + def percent_complete + ((closed_items_count * 100) / total_items_count).abs + rescue ZeroDivisionError + 0 + end + + def remaining_days + return 0 if !due_date || expired? + + (due_date - Date.today).to_i + end +end diff --git a/app/models/global_label.rb b/app/models/global_label.rb index 0171f7d54b7..ddd4bad5c21 100644 --- a/app/models/global_label.rb +++ b/app/models/global_label.rb @@ -2,16 +2,19 @@ class GlobalLabel attr_accessor :title, :labels alias_attribute :name, :title + delegate :color, :description, to: :@first_label + def self.build_collection(labels) labels = labels.group_by(&:title) - labels.map do |title, label| - new(title, label) + labels.map do |title, labels| + new(title, labels) end end def initialize(title, labels) @title = title @labels = labels + @first_label = labels.find { |lbl| lbl.description.present? } || labels.first end end diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index 7ee276255a0..97bd79af083 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -1,4 +1,6 @@ class GlobalMilestone + include Milestoneish + attr_accessor :title, :milestones alias_attribute :name, :title @@ -28,33 +30,7 @@ class GlobalMilestone end def projects - milestones.map { |milestone| milestone.project } - end - - def issue_count - milestones.map { |milestone| milestone.issues.count }.sum - end - - def merge_requests_count - milestones.map { |milestone| milestone.merge_requests.count }.sum - end - - def open_items_count - milestones.map { |milestone| milestone.open_items_count }.sum - end - - def closed_items_count - milestones.map { |milestone| milestone.closed_items_count }.sum - end - - def total_items_count - milestones.map { |milestone| milestone.total_items_count }.sum - end - - def percent_complete - ((closed_items_count * 100) / total_items_count).abs - rescue ZeroDivisionError - 0 + @projects ||= Project.for_milestones(milestones.map(&:id)) end def state @@ -76,35 +52,20 @@ class GlobalMilestone end def issues - @issues ||= milestones.map(&:issues).flatten.group_by(&:state) + @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project) end def merge_requests - @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) + @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project) end def participants @participants ||= milestones.map(&:participants).flatten.compact.uniq end - def opened_issues - issues.values_at("opened", "reopened").compact.flatten - end - - def closed_issues - issues['closed'] - end - - def opened_merge_requests - merge_requests.values_at("opened", "reopened").compact.flatten - end - - def closed_merge_requests - merge_requests.values_at("closed", "merged", "locked").compact.flatten - end - - def complete? - total_items_count == closed_items_count + def labels + @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten) + .sort_by!(&:title) end def due_date diff --git a/app/models/label.rb b/app/models/label.rb index c34f4e4ba60..5ff644b8426 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -48,10 +48,15 @@ class Label < ActiveRecord::Base '~' end + ## # Pattern used to extract label references from text + # + # This pattern supports cross-project references. + # def self.reference_pattern %r{ - #{reference_prefix} + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)} (?: (?<label_id>\d+) | # Integer-based label ID, or (?<label_name> @@ -62,24 +67,31 @@ class Label < ActiveRecord::Base }x end + def self.link_reference_pattern + nil + end + + ## # Returns the String necessary to reference this Label in Markdown # # format - Symbol format to use (default: :id, optional: :name) # - # Note that its argument differs from other objects implementing Referable. If - # a non-Symbol argument is given (such as a Project), it will default to :id. - # # Examples: # - # Label.first.to_reference # => "~1" - # Label.first.to_reference(:name) # => "~\"bug\"" + # Label.first.to_reference # => "~1" + # Label.first.to_reference(format: :name) # => "~\"bug\"" + # Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1" # # Returns a String - def to_reference(format = :id) - if format == :name && !name.include?('"') - %(#{self.class.reference_prefix}"#{name}") + # + def to_reference(from_project = nil, format: :id) + format_reference = label_format_reference(format) + reference = "#{self.class.reference_prefix}#{format_reference}" + + if cross_project_reference?(from_project) + project.to_reference + reference else - "#{self.class.reference_prefix}#{id}" + reference end end @@ -98,4 +110,16 @@ class Label < ActiveRecord::Base def template? template end + + private + + def label_format_reference(format = :id) + raise StandardError, 'Unknown format' unless [:id, :name].include?(format) + + if format == :name && !name.include?('"') + %("#{name}") + else + id + end + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index cf26183c254..c1e18bb3cc5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -137,9 +137,7 @@ class MergeRequest < ActiveRecord::Base scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) } - scope :opened, -> { with_states(:opened, :reopened) } scope :merged, -> { with_state(:merged) } - scope :closed, -> { with_state(:closed) } scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :join_project, -> { joins(:target_project) } @@ -537,6 +535,29 @@ class MergeRequest < ActiveRecord::Base end end + def diverged_commits_count + cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits") + + if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha + cache = { + source_sha: source_sha, + target_sha: target_sha, + diverged_commits_count: compute_diverged_commits_count + } + Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache) + end + + cache[:diverged_commits_count] + end + + def compute_diverged_commits_count + Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size + end + + def diverged_from_target_branch? + diverged_commits_count > 0 + end + def ci_commit @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index df08d3a6dfb..33884118595 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -17,7 +17,7 @@ class MergeRequestDiff < ActiveRecord::Base include Sortable # Prevent store of diff if commits amount more then 500 - COMMITS_SAFE_SIZE = 500 + COMMITS_SAFE_SIZE = 100 belongs_to :merge_request diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 7dc2f909b2f..e3969f32dd6 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base include Sortable include Referable include StripAttribute + include Milestoneish belongs_to :project has_many :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :merge_requests - has_many :participants, through: :issues, source: :assignee + has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee scope :active, -> { with_state(:active) } scope :closed, -> { with_state(:closed) } @@ -92,30 +93,6 @@ class Milestone < ActiveRecord::Base end end - def open_items_count - self.issues.opened.count + self.merge_requests.opened.count - end - - def closed_items_count - self.issues.closed.count + self.merge_requests.closed_and_merged.count - end - - def total_items_count - self.issues.count + self.merge_requests.count - end - - def percent_complete - ((closed_items_count * 100) / total_items_count).abs - rescue ZeroDivisionError - 0 - end - - def remaining_days - return 0 if !due_date || expired? - - (due_date - Date.today).to_i - end - def expires_at if due_date if due_date.past? diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index 9cee3b70cb3..452f3913eef 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -10,7 +10,6 @@ # created_at :datetime # updated_at :datetime # file_name :string(255) -# expires_at :datetime # type :string(255) # visibility_level :integer default(0), not null # diff --git a/app/models/project.rb b/app/models/project.rb index 148eab692ff..426464dee81 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -151,6 +151,7 @@ class Project < ActiveRecord::Base has_many :releases, dependent: :destroy has_many :lfs_objects_projects, dependent: :destroy has_many :lfs_objects, through: :lfs_objects_projects + has_many :todos, dependent: :destroy has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" @@ -215,6 +216,7 @@ class Project < ActiveRecord::Base scope :public_only, -> { where(visibility_level: Project::PUBLIC) } scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :non_archived, -> { where(archived: false) } + scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } state_machine :import_status, initial: :none do event :import_start do diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 9e2c1b0e18e..1f7d85a5f3d 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -10,7 +10,6 @@ # created_at :datetime # updated_at :datetime # file_name :string(255) -# expires_at :datetime # type :string(255) # visibility_level :integer default(0), not null # @@ -23,6 +22,4 @@ class ProjectSnippet < Snippet # Scopes scope :fresh, -> { order("created_at DESC") } - scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } - scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } end diff --git a/app/models/repository.rb b/app/models/repository.rb index c135ab61f6a..6441cd87e87 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -133,18 +133,18 @@ class Repository rugged.branches.create(branch_name, target) end - expire_branches_cache + after_create_branch find_branch(branch_name) end def add_tag(tag_name, ref, message = nil) - expire_tags_cache + before_push_tag gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) end def rm_branch(user, branch_name) - expire_branches_cache + before_remove_branch branch = find_branch(branch_name) oldrev = branch.try(:target) @@ -155,12 +155,12 @@ class Repository rugged.branches.delete(branch_name) end - expire_branches_cache + after_remove_branch true end def rm_tag(tag_name) - expire_tags_cache + before_remove_tag gitlab_shell.rm_tag(path_with_namespace, tag_name) end @@ -183,6 +183,14 @@ class Repository end end + def branch_count + @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count } + end + + def tag_count + @tag_count ||= cache.fetch(:tag_count) { raw_repository.rugged.tags.count } + end + # Return repo size in megabytes # Cached in redis def size @@ -278,6 +286,16 @@ class Repository @has_visible_content = nil end + def expire_branch_count_cache + cache.expire(:branch_count) + @branch_count = nil + end + + def expire_tag_count_cache + cache.expire(:tag_count) + @tag_count = nil + end + def rebuild_cache cache_keys.each do |key| cache.expire(key) @@ -313,9 +331,17 @@ class Repository expire_root_ref_cache end - # Runs code before creating a new tag. - def before_create_tag + # Runs code before pushing (= creating or removing) a tag. + def before_push_tag expire_cache + expire_tags_cache + expire_tag_count_cache + end + + # Runs code before removing a tag. + def before_remove_tag + expire_tags_cache + expire_tag_count_cache end # Runs code after a repository has been forked/imported. @@ -330,12 +356,21 @@ class Repository # Runs code after a new branch has been created. def after_create_branch + expire_branches_cache expire_has_visible_content_cache + expire_branch_count_cache + end + + # Runs code before removing an existing branch. + def before_remove_branch + expire_branches_cache end # Runs code after an existing branch has been removed. def after_remove_branch expire_has_visible_content_cache + expire_branch_count_cache + expire_branches_cache end def method_missing(m, *args, &block) @@ -812,6 +847,12 @@ class Repository raw_repository.ls_files(actual_ref) end + def main_language + unless empty? + Linguist::Repository.new(rugged, rugged.head.target_id).language + end + end + private def cache diff --git a/app/models/snippet.rb b/app/models/snippet.rb index f876be7a4c8..dd3925c7a7d 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -10,7 +10,6 @@ # created_at :datetime # updated_at :datetime # file_name :string(255) -# expires_at :datetime # type :string(255) # visibility_level :integer default(0), not null # @@ -46,8 +45,6 @@ class Snippet < ActiveRecord::Base scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) } scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) } scope :fresh, -> { order("created_at DESC") } - scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } - scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } participant :author, :notes @@ -111,10 +108,6 @@ class Snippet < ActiveRecord::Base nil end - def expired? - expires_at && expires_at < Time.current - end - def visibility_level_field visibility_level end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 9ba200f7bde..93a16e88967 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -14,6 +14,7 @@ class GitPushService < BaseService # 3. Recognizes cross-references from commit messages # 4. Executes the project's web hooks # 5. Executes the project's services + # 6. Checks if the project's main language has changed # def execute @project.repository.after_push_commit(branch_name) @@ -42,11 +43,24 @@ class GitPushService < BaseService @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev]) process_commit_messages end + # Checks if the main language has changed in the project and if so + # it updates it accordingly + update_main_language # Update merge requests that may be affected by this push. A new branch # could cause the last commit of a merge request to change. update_merge_requests end + def update_main_language + current_language = @project.repository.main_language + + unless current_language == @project.main_language + return @project.update_attributes(main_language: current_language) + end + + true + end + protected def update_merge_requests @@ -96,7 +110,9 @@ class GitPushService < BaseService # a different branch. closed_issues = commit.closes_issues(current_user) closed_issues.each do |issue| - Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) + if can?(current_user, :update_issue, issue) + Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) + end end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index a62c5fc4fc4..c88c7672805 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -2,7 +2,7 @@ class GitTagPushService attr_accessor :project, :user, :push_data def execute(project, user, oldrev, newrev, ref) - project.repository.before_create_tag + project.repository.before_push_tag @project, @user = project, user @push_data = build_push_data(oldrev, newrev, ref) diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index 8f25c5e2496..ebb67c7db65 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -21,7 +21,9 @@ module MergeRequests closed_issues = merge_request.closes_issues(current_user) closed_issues.each do |issue| - Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request) + if can?(current_user, :update_issue, issue) + Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request) + end end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index edced010811..58a861ee08e 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -66,7 +66,7 @@ class SystemNoteService def self.change_label(noteable, project, author, added_labels, removed_labels) labels_count = added_labels.count + removed_labels.count - references = ->(label) { label.to_reference(:id) } + references = ->(label) { label.to_reference(format: :id) } added_labels = added_labels.map(&references).join(' ') removed_labels = removed_labels.map(&references).join(' ') diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 40f88261c10..9da3fcbd986 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -15,7 +15,7 @@ .nav-controls = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2" - = render 'explore/projects/dropdown' + = render 'shared/projects/dropdown' - if current_user.can_create_project? = link_to new_project_path, class: 'btn btn-new' do = icon('plus') diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml deleted file mode 100644 index 1408ebdd5dc..00000000000 --- a/app/views/dashboard/milestones/_issue.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid } - %span.milestone-row - - project = issue.project - %strong #{project.name_with_namespace} · - = link_to [project.namespace.becomes(Namespace), project, issue] do - %span.cgray ##{issue.iid} - = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title - .pull-right.assignee-icon - - if issue.assignee - = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16" diff --git a/app/views/dashboard/milestones/_issues.html.haml b/app/views/dashboard/milestones/_issues.html.haml deleted file mode 100644 index 9f350b772bd..00000000000 --- a/app/views/dashboard/milestones/_issues.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -.panel.panel-default - .panel-heading= title - %ul{ class: "well-list issues-sortable-list" } - - if issues - - issues.each do |issue| - = render 'issue', issue: issue diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml deleted file mode 100644 index 77c46de030b..00000000000 --- a/app/views/dashboard/milestones/_merge_request.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid } - %span.milestone-row - - project = merge_request.project - %strong #{project.name_with_namespace} · - = link_to [project.namespace.becomes(Namespace), project, merge_request] do - %span.cgray ##{merge_request.iid} - = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title - .pull-right.assignee-icon - - if merge_request.assignee - = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16" diff --git a/app/views/dashboard/milestones/_merge_requests.html.haml b/app/views/dashboard/milestones/_merge_requests.html.haml deleted file mode 100644 index 50057e2c636..00000000000 --- a/app/views/dashboard/milestones/_merge_requests.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -.panel.panel-default - .panel-heading= title - %ul{ class: "well-list merge_requests-sortable-list" } - - if merge_requests - - merge_requests.each do |merge_request| - = render 'merge_request', merge_request: merge_request diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml index 7c882a32702..6173ca6ab9b 100644 --- a/app/views/dashboard/milestones/_milestone.html.haml +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -1,25 +1,6 @@ -%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - .row - .col-sm-6 - %strong - = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) - .col-sm-6 - .pull-right.light #{milestone.percent_complete}% complete - .row - .col-sm-6 - = link_to issues_dashboard_path(milestone_title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' - · - = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' - .col-sm-6 - = milestone_progress_bar(milestone) - .row - .col-sm-6 - .expiration - = render 'shared/milestone_expired', milestone: milestone - .projects - - milestone.milestones.each do |milestone| - = link_to milestone_path(milestone) do - %span.label.label-gray - = milestone.project.name_with_namespace += render 'shared/milestones/milestone', + milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title), + issues_path: issues_dashboard_path(milestone_title: milestone.title), + merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title), + milestone: milestone, + dashboard: true diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 3810267577c..60c84a26420 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -1,105 +1,5 @@ -- page_title @milestone.title, "Milestones" - header_title "Milestones", dashboard_milestones_path -.detail-page-header - .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" } - - if @milestone.closed? - Closed - - else - Open - %span.identifier - Milestone #{@milestone.title} - -.detail-page-description.gray-content-block.second-block - %h2.title - = markdown escape_once(@milestone.title), pipeline: :single_line - -- if @milestone.complete? && @milestone.active? - .alert.alert-success.prepend-top-default - %span All issues for this milestone are closed. Navigate to the project to close the milestone. - -.table-holder - %table.table - %thead - %tr - %th Project - %th Open issues - %th State - %th Due date - - @milestone.milestones.each do |milestone| - %tr - %td - = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) - %td - = milestone.issues.opened.count - %td - - if milestone.closed? - Closed - - else - Open - %td - = milestone.expires_at - -.context - %p.lead - Progress: - #{@milestone.closed_items_count} closed - – - #{@milestone.open_items_count} open - = milestone_progress_bar(@milestone) - -%ul.nav-links.no-top.no-bottom - %li.active - = link_to '#tab-issues', 'data-toggle' => 'tab' do - Issues - %span.badge= @milestone.issue_count - %li - = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do - Merge Requests - %span.badge= @milestone.merge_requests_count - %li - = link_to '#tab-participants', 'data-toggle' => 'tab' do - Participants - %span.badge= @milestone.participants.count - -.tab-content - .tab-pane.active#tab-issues - .gray-content-block.middle-block - .pull-right - = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped" - - .oneline - All issues in this milestone - - .row.prepend-top-default - .col-md-6 - = render 'issues', title: "Open", issues: @milestone.opened_issues - .col-md-6 - = render 'issues', title: "Closed", issues: @milestone.closed_issues - - .tab-pane#tab-merge-requests - .gray-content-block.middle-block - .pull-right - = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped" - - .oneline - All merge requests in this milestone - - .row.prepend-top-default - .col-md-6 - = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests - .col-md-6 - = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests - - .tab-pane#tab-participants - .gray-content-block.middle-block - .oneline - All participants to this milestone - %ul.bordered-list - - @milestone.participants.each do |user| - %li - = link_to user, title: user.name, class: "darken" do - = image_tag avatar_icon(user, 32), class: "avatar s32" - %strong= truncate(user.name, lenght: 40) - %br - %small.cgray= user.username += render 'shared/milestones/top', milestone: @milestone += render 'shared/milestones/summary', milestone: @milestone += render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index f878d36e739..45cfe3da188 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -4,7 +4,10 @@ .todo-title %span.author-name - = link_to_author todo + - if todo.author + = link_to_author(todo) + - else + (removed) %span.todo-label = todo_action_name(todo) = todo_target_link(todo) diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml deleted file mode 100644 index a4b4cd8d6c7..00000000000 --- a/app/views/explore/projects/_dropdown.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_updated - %b.caret - %ul.dropdown-menu.dropdown-menu-align-right - %li - = link_to explore_projects_filter_path(sort: sort_value_name) do - = sort_title_name - = link_to explore_projects_filter_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to explore_projects_filter_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 39e3e8e2738..cd485da5104 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -10,11 +10,11 @@ %b.caret %ul.dropdown-menu %li - = link_to explore_projects_filter_path(visibility_level: nil) do + = link_to filter_projects_path(visibility_level: nil) do Any - Gitlab::VisibilityLevel.values.each do |level| %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } - = link_to explore_projects_filter_path(visibility_level: level) do + = link_to filter_projects_path(visibility_level: level) do = visibility_level_icon(level) = visibility_level_label(level) @@ -30,11 +30,11 @@ %b.caret %ul.dropdown-menu %li - = link_to explore_projects_filter_path(tag: nil) do + = link_to filter_projects_path(tag: nil) do Any - @tags.each do |tag| %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } - = link_to explore_projects_filter_path(tag: tag.name) do - %i.fa.fa-tag + = link_to filter_projects_path(tag: tag.name) do + = icon('tag') = tag.name diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 794aa57b55a..7cd8e9bea46 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -3,9 +3,10 @@ = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| - if @projects.present? = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false - - if can? current_user, :create_projects, @group - = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do - = icon('plus') - New Project + = render 'shared/projects/dropdown' + - if can? current_user, :create_projects, @group + = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do + = icon('plus') + New Project = render 'shared/projects/list', projects: @projects, stars: false, skip_namespace: true diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml deleted file mode 100644 index 9b85d83d6d8..00000000000 --- a/app/views/groups/milestones/_issue.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid } - %span.milestone-row - - project = issue.project - %strong #{project.name} · - = link_to [project.namespace.becomes(Namespace), project, issue] do - %span.cgray ##{issue.iid} - = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title - .pull-right.assignee-icon - - if issue.assignee - = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: '' diff --git a/app/views/groups/milestones/_issues.html.haml b/app/views/groups/milestones/_issues.html.haml deleted file mode 100644 index 9f350b772bd..00000000000 --- a/app/views/groups/milestones/_issues.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -.panel.panel-default - .panel-heading= title - %ul{ class: "well-list issues-sortable-list" } - - if issues - - issues.each do |issue| - = render 'issue', issue: issue diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml deleted file mode 100644 index e3aa4aad198..00000000000 --- a/app/views/groups/milestones/_merge_request.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid } - %span.milestone-row - - project = merge_request.project - %strong #{project.name} · - = link_to [project.namespace.becomes(Namespace), project, merge_request] do - %span.cgray ##{merge_request.iid} - = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title - .pull-right.assignee-icon - - if merge_request.assignee - = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: '' diff --git a/app/views/groups/milestones/_merge_requests.html.haml b/app/views/groups/milestones/_merge_requests.html.haml deleted file mode 100644 index 50057e2c636..00000000000 --- a/app/views/groups/milestones/_merge_requests.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -.panel.panel-default - .panel-heading= title - %ul{ class: "well-list merge_requests-sortable-list" } - - if merge_requests - - merge_requests.each do |merge_request| - = render 'merge_request', merge_request: merge_request diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml index a20bf75bc39..4c4e0a26728 100644 --- a/app/views/groups/milestones/_milestone.html.haml +++ b/app/views/groups/milestones/_milestone.html.haml @@ -1,29 +1,5 @@ -%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - .row - .col-sm-6 - %strong - = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) - .col-sm-6 - .pull-right.light #{milestone.percent_complete}% complete - .row - .col-sm-6 - = link_to issues_group_path(@group, milestone_title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' - · - = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' - .col-sm-6 - = milestone_progress_bar(milestone) - .row - .col-sm-6 - %div - - milestone.milestones.each do |milestone| - = link_to milestone_path(milestone) do - %span.label.label-gray - = milestone.project.name - .col-sm-6 - - if can?(current_user, :admin_milestones, @group) - - if milestone.closed? - = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen" - - else - = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close" += render 'shared/milestones/milestone', + milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title), + issues_path: issues_group_path(@group, milestone_title: milestone.title), + merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title), + milestone: milestone diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 1233da85524..fb6f0da28f8 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -1,112 +1,4 @@ -- page_title @milestone.title, "Milestones" = render "header_title" - -.detail-page-header - .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" } - - if @milestone.closed? - Closed - - else - Open - %span.identifier - Milestone #{@milestone.title} - .pull-right - - if can?(current_user, :admin_milestones, @group) - - if @milestone.active? - = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close" - - else - = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" - -.detail-page-description.gray-content-block.second-block - %h2.title - = markdown escape_once(@milestone.title), pipeline: :single_line - -- if @milestone.complete? && @milestone.active? - .alert.alert-success.prepend-top-default - %span All issues for this milestone are closed. You may close the milestone now. - -.table-holder - %table.table - %thead - %tr - %th Project - %th Open issues - %th State - %th Due date - - @milestone.milestones.each do |milestone| - %tr - %td - = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) - %td - = milestone.issues.opened.count - %td - - if milestone.closed? - Closed - - else - Open - %td - = milestone.expires_at - -.context - %p.lead - Progress: - #{@milestone.closed_items_count} closed - – - #{@milestone.open_items_count} open - = milestone_progress_bar(@milestone) - -%ul.nav-links.no-top.no-bottom - %li.active - = link_to '#tab-issues', 'data-toggle' => 'tab' do - Issues - %span.badge= @milestone.issue_count - %li - = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do - Merge Requests - %span.badge= @milestone.merge_requests_count - %li - = link_to '#tab-participants', 'data-toggle' => 'tab' do - Participants - %span.badge= @milestone.participants.count - -.tab-content - .tab-pane.active#tab-issues - .gray-content-block.middle-block - .pull-right - = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped" - - .oneline - All issues in this milestone - - .row.prepend-top-default - .col-md-6 - = render 'issues', title: "Open", issues: @milestone.opened_issues - .col-md-6 - = render 'issues', title: "Closed", issues: @milestone.closed_issues - - .tab-pane#tab-merge-requests - .gray-content-block.middle-block - .pull-right - = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped" - - .oneline - All merge requests in this milestone - - .row.prepend-top-default - .col-md-6 - = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests - .col-md-6 - = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests - - .tab-pane#tab-participants - .gray-content-block.middle-block - .oneline - All participants to this milestone - - %ul.bordered-list - - @milestone.participants.each do |user| - %li - = link_to user, title: user.name, class: "darken" do - = image_tag avatar_icon(user, 32), class: "avatar s32" - %strong= truncate(user.name, lenght: 40) - %br - %small.cgray= user.username += render 'shared/milestones/top', milestone: @milestone, group: @group += render 'shared/milestones/summary', milestone: @milestone += render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml index 3c11b97921f..18caddabd39 100644 --- a/app/views/projects/blob/_image.html.haml +++ b/app/views/projects/blob/_image.html.haml @@ -6,4 +6,4 @@ - blob = sanitize_svg(blob) %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"} - else - %img{src: namespace_project_raw_path(@project.namespace, @project, @id)} + %img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))} diff --git a/app/views/projects/branches/destroy.js.haml b/app/views/projects/branches/destroy.js.haml index 882a4d0c5e2..a21ddaf4930 100644 --- a/app/views/projects/branches/destroy.js.haml +++ b/app/views/projects/branches/destroy.js.haml @@ -1 +1 @@ -$('.js-totalbranch-count').html("#{@repository.branches.size}") +$('.js-totalbranch-count').html("#{@repository.branch_count}") diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml index ce60fbdf032..bac9e244d36 100644 --- a/app/views/projects/commits/_commit_list.html.haml +++ b/app/views/projects/commits/_commit_list.html.haml @@ -1,11 +1,14 @@ +- commits, hidden = limited_commits(@commits) +- commits = Commit.decorate(commits, @project) + %div.panel.panel-default .panel-heading Commits (#{@commits.count}) - - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + - if hidden > 0 %ul.well-list - - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), @project).each do |commit| + - commits.each do |commit| = render "projects/commits/inline_commit", commit: commit, project: @project %li.warning-row.unstyled - other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. + #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues. - else - %ul.well-list= render Commit.decorate(@commits, @project), project: @project + %ul.well-list= render commits, project: @project diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index 6c631228002..a7e3c2478c2 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -1,7 +1,9 @@ - unless defined?(project) - project = @project -- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits| +- commits, hidden = limited_commits(@commits) + +- commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits| .row.commits-row .col-md-2.hidden-xs.hidden-sm %h5.commits-row-date @@ -13,3 +15,7 @@ %ul.bordered-list = render commits, project: project %hr.lists-separator + +- if hidden > 0 + .alert.alert-warning + #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues. diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 498c5e05b32..7a5b0d993db 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -15,9 +15,9 @@ = nav_link(html_options: {class: branches_tab_class}) do = link_to namespace_project_branches_path(@project.namespace, @project) do Branches - %span.badge.js-totalbranch-count= @repository.branches.size + %span.badge.js-totalbranch-count= @repository.branch_count = nav_link(controller: [:tags, :releases]) do = link_to namespace_project_tags_path(@project.namespace, @project) do Tags - %span.badge.js-totaltags-count= @repository.tags.length + %span.badge.js-totaltags-count= @repository.tag_count diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 7b5e2991c09..b262892ac65 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -34,6 +34,8 @@ %span into = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do = @merge_request.target_branch + - if @merge_request.open? && @merge_request.diverged_from_target_branch? + %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/widget/show.html.haml" diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml deleted file mode 100644 index ca51b8c745d..00000000000 --- a/app/views/projects/milestones/_issue.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) } - %span - = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title - .issue-detail - = link_to [@project.namespace.becomes(Namespace), @project, issue] do - %span.issue-number ##{issue.iid} - - issue.labels.each do |label| - = render_colored_label(label) - - if issue.assignee - = image_tag avatar_icon(issue.assignee, 16), class: "avatar s24", alt: '' diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml deleted file mode 100644 index 6f8a341e478..00000000000 --- a/app/views/projects/milestones/_issues.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.panel.panel-default - .panel-heading - = title - .pull-right= issues.size - %ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id } - - issues.sort_by(&:position).each do |issue| - = render 'issue', issue: issue diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml deleted file mode 100644 index a1033607c5d..00000000000 --- a/app/views/projects/milestones/_merge_request.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) } - %span.str-truncated - = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do - %span.cgray ##{merge_request.iid} - = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title - .pull-right.assignee-icon - - if merge_request.assignee - = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: '' diff --git a/app/views/projects/milestones/_merge_requests.html.haml b/app/views/projects/milestones/_merge_requests.html.haml deleted file mode 100644 index 9a5a02af215..00000000000 --- a/app/views/projects/milestones/_merge_requests.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -.panel.panel-default - .panel-heading= title - %ul{ class: "well-list merge_requests-sortable-list", id: "merge_requests-list-#{id}", "data-state" => id } - - merge_requests.sort_by(&:position).each do |merge_request| - = render 'merge_request', merge_request: merge_request diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 67d95ab0364..77b566db6b6 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -1,31 +1,5 @@ -%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) } - .row - .col-sm-6 - %strong - = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) - - .col-sm-6 - .pull-right.light #{milestone.percent_complete}% complete - .row - .col-sm-6 - = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do - = pluralize milestone.issues.count, 'Issue' - · - = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do - = pluralize milestone.merge_requests.count, 'Merge Request' - .col-sm-6 - = milestone_progress_bar(milestone) - - .row - .col-sm-6 - = render 'shared/milestone_expired', milestone: milestone - .col-sm-6 - - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do - = icon('pencil-square-o') - Edit - \ - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close" - = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do - = icon('trash-o') - Delete += render 'shared/milestones/milestone', + milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), + issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title), + merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title), + milestone: milestone diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 2cae1ac4e2c..b4597043a27 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -42,102 +42,9 @@ = preserve do = markdown @milestone.description -- if @milestone.issues.any? && @milestone.can_be_closed? +- if @milestone.complete? && @milestone.active? .alert.alert-success.prepend-top-default %span All issues for this milestone are closed. You may close milestone now. -.context.prepend-top-default - .milestone-summary - %h4 Progress - %strong= @milestone.issues.count - issues: - %span.milestone-stat - %strong= @milestone.open_items_count - open and - %strong= @milestone.closed_items_count - closed - %span.milestone-stat - %strong== #{@milestone.percent_complete}% - complete - %span.milestone-stat - %span.remaining-days= milestone_remaining_days(@milestone) - %span.pull-right.tab-issues-buttons - - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do - %i.fa.fa-plus - New Issue - - if can?(current_user, :read_issue, @project) - = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped" - %span.pull-right.tab-merge-requests-buttons.hidden - - if can?(current_user, :read_merge_request, @project) - = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped" - - = milestone_progress_bar(@milestone) - -%ul.nav-links.no-top.no-bottom - %li.active - = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do - Issues - %span.badge= @issues.count - %li - = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do - Merge Requests - %span.badge= @merge_requests.count - %li - = link_to '#tab-participants', 'data-toggle' => 'tab' do - Participants - %span.badge= @users.count - %li - = link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do - Labels - %span.badge= @labels.count - -.tab-content.milestone-content - .tab-pane.active#tab-issues - .row.prepend-top-default - .col-md-4 - = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned') - .col-md-4 - = render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned, id: 'ongoing') - .col-md-4 - = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed') - - .tab-pane#tab-merge-requests - .row.prepend-top-default - .col-md-3 - = render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned') - .col-md-3 - = render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing') - .col-md-3 - = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed') - .col-md-3 - .panel.panel-primary - .panel-heading Merged - %ul.well-list - - @merge_requests.merged.each do |merge_request| - = render 'merge_request', merge_request: merge_request - - .tab-pane#tab-participants - %ul.bordered-list - - @users.each do |user| - %li - = link_to user, title: user.name, class: "darken" do - = image_tag avatar_icon(user, 32), class: "avatar s32" - %strong= truncate(user.name, lenght: 40) - %br - %small.cgray= user.username - - .tab-pane#tab-labels - %ul.bordered-list.manage-labels-list - - @labels.each do |label| - %li - = render_colored_label(label) - - args = [@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title, label_name: label.title] - - options = args.extract_options! - - %span.issues-count - = link_to namespace_project_issues_path(*args, options.merge(state: 'opened')) do - = pluralize label.open_issues_count, 'open issue' - %span.issues-count - = link_to namespace_project_issues_path(*args, options.merge(state: 'closed')) do - = pluralize label.closed_issues_count, 'closed issue' += render 'shared/milestones/summary', milestone: @milestone, project: @project += render 'shared/milestones/tabs', milestone: @milestone diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml new file mode 100644 index 00000000000..f7c6fc14adf --- /dev/null +++ b/app/views/shared/milestones/_issuable.html.haml @@ -0,0 +1,25 @@ +-# @project is present when viewing Project's milestone +- project = @project || issuable.project +- assignee = issuable.assignee +- issuable_type = issuable.class.table_name +- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type] + +%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) } + %span + - if show_project_name + %strong #{project.name} · + - elsif show_full_project_name + %strong #{project.name_with_namespace} · + = link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title + %div{class: 'issuable-detail'} + = link_to [project.namespace.becomes(Namespace), project, issuable] do + %span{ class: 'issuable-number' }>= issuable.to_reference + + - issuable.labels.each do |label| + = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do + - render_colored_label(label) + + - if assignee + = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }), + class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do + - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '') diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml new file mode 100644 index 00000000000..8619939dde7 --- /dev/null +++ b/app/views/shared/milestones/_issuables.html.haml @@ -0,0 +1,16 @@ +- show_counter = local_assigns.fetch(:show_counter, false) +- primary = local_assigns.fetch(:primary, false) +- panel_class = primary ? 'panel-primary' : 'panel-default' + +.panel{ class: panel_class } + .panel-heading + = title + - if show_counter + .pull-right= issuables.size + + - class_prefix = dom_class(issuables).pluralize + %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id } + = render partial: 'shared/milestones/issuable', + collection: issuables.sort_by(&:position), + as: :issuable, + locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name } diff --git a/app/views/shared/milestones/_issues_tab.html.haml b/app/views/shared/milestones/_issues_tab.html.haml new file mode 100644 index 00000000000..a8db7f8a556 --- /dev/null +++ b/app/views/shared/milestones/_issues_tab.html.haml @@ -0,0 +1,10 @@ +- args = { show_project_name: local_assigns.fetch(:show_project_name, false), + show_full_project_name: local_assigns.fetch(:show_full_project_name, false) } + +.row.prepend-top-default + .col-md-4 + = render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true) + .col-md-4 + = render 'shared/milestones/issuables', args.merge(title: 'Ongoing Issues (open and assigned)', issuables: issues.opened.assigned, id: 'ongoing', show_counter: true) + .col-md-4 + = render 'shared/milestones/issuables', args.merge(title: 'Completed Issues (closed)', issuables: issues.closed, id: 'closed', show_counter: true) diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml new file mode 100644 index 00000000000..ba27bafd1bc --- /dev/null +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -0,0 +1,18 @@ +%ul.bordered-list.manage-labels-list + - labels.each do |label| + - options = { milestone_title: @milestone.title, label_name: label.title } + + %li + %span.label-row + = link_to milestones_label_path(options) do + - render_colored_label(label) + %span.prepend-left-10 + = markdown(label.description, pipeline: :single_line) + + .pull-right + %strong.issues-count + = link_to milestones_label_path(options.merge(state: 'opened')) do + - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue' + %strong.issues-count + = link_to milestones_label_path(options.merge(state: 'closed')) do + - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue' diff --git a/app/views/shared/milestones/_merge_requests_tab.haml b/app/views/shared/milestones/_merge_requests_tab.haml new file mode 100644 index 00000000000..c29d8ee6737 --- /dev/null +++ b/app/views/shared/milestones/_merge_requests_tab.haml @@ -0,0 +1,12 @@ +- args = { show_project_name: local_assigns.fetch(:show_project_name, false), + show_full_project_name: local_assigns.fetch(:show_full_project_name, false) } + +.row.prepend-top-default + .col-md-3 + = render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned') + .col-md-3 + = render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing') + .col-md-3 + = render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed') + .col-md-3 + = render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true) diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml new file mode 100644 index 00000000000..f01138af3f0 --- /dev/null +++ b/app/views/shared/milestones/_milestone.html.haml @@ -0,0 +1,45 @@ +- dashboard = local_assigns[:dashboard] +- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first) + +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id } + .row + .col-sm-6 + %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path + .col-sm-6 + .pull-right.light #{milestone.percent_complete}% complete + .row + .col-sm-6 + = link_to pluralize(milestone.issues.size, 'Issue'), issues_path + · + = link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path + .col-sm-6= milestone_progress_bar(milestone) + - if milestone.is_a?(GlobalMilestone) + .row + .col-sm-6 + .expiration= render('shared/milestone_expired', milestone: milestone) + .projects + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = dashboard ? milestone.project.name_with_namespace : milestone.project.name + - if @group + .col-sm-6 + - if can?(current_user, :admin_milestones, @group) + - if milestone.closed? + = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen" + - else + = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close" + + - if @project + .row + .col-sm-6= render('shared/milestone_expired', milestone: milestone) + .col-sm-6 + - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? + = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do + = icon('pencil-square-o') + Edit + \ + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close" + = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do + = icon('trash-o') + Delete diff --git a/app/views/shared/milestones/_participants_tab.html.haml b/app/views/shared/milestones/_participants_tab.html.haml new file mode 100644 index 00000000000..67ae85ac276 --- /dev/null +++ b/app/views/shared/milestones/_participants_tab.html.haml @@ -0,0 +1,8 @@ +%ul.bordered-list + - users.each do |user| + %li + = link_to user, title: user.name, class: "darken" do + = image_tag avatar_icon(user, 32), class: "avatar s32" + %strong= truncate(user.name, lenght: 40) + %br + %small.cgray= user.username diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml new file mode 100644 index 00000000000..59d4ae29f79 --- /dev/null +++ b/app/views/shared/milestones/_summary.html.haml @@ -0,0 +1,28 @@ +- project = local_assigns[:project] + +.context.prepend-top-default + .milestone-summary + %h4 Progress + %strong= milestone.issues.size + issues: + %span.milestone-stat + %strong= milestone.issues.opened.size + open and + %strong= milestone.issues.closed.size + closed + %span.milestone-stat + %strong== #{milestone.percent_complete}% + complete + + %span.milestone-stat + %span.remaining-days= milestone_remaining_days(milestone) + %span.pull-right.tab-issues-buttons + - if project && can?(current_user, :create_issue, project) + = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do + %i.fa.fa-plus + New Issue + = link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped" + %span.pull-right.tab-merge-requests-buttons.hidden + = link_to 'Browse Merge Requests', milestones_browse_issuables_path(milestone, type: :merge_requests), class: "btn btn-grouped" + + = milestone_progress_bar(milestone) diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml new file mode 100644 index 00000000000..57d7ee85a3b --- /dev/null +++ b/app/views/shared/milestones/_tabs.html.haml @@ -0,0 +1,30 @@ +%ul.nav-links.no-top.no-bottom + %li.active + = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do + Issues + %span.badge= milestone.issues.size + %li + = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do + Merge Requests + %span.badge= milestone.merge_requests.size + %li + = link_to '#tab-participants', 'data-toggle' => 'tab' do + Participants + %span.badge= milestone.participants.count + %li + = link_to '#tab-labels', 'data-toggle' => 'tab' do + Labels + %span.badge= milestone.labels.count + +- show_project_name = local_assigns.fetch(:show_project_name, false) +- show_full_project_name = local_assigns.fetch(:show_full_project_name, false) + +.tab-content.milestone-content + .tab-pane.active#tab-issues + = render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name + .tab-pane#tab-merge-requests + = render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name + .tab-pane#tab-participants + = render 'shared/milestones/participants_tab', users: milestone.participants + .tab-pane#tab-labels + = render 'shared/milestones/labels_tab', labels: milestone.labels diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml new file mode 100644 index 00000000000..4cf1d948b5b --- /dev/null +++ b/app/views/shared/milestones/_top.html.haml @@ -0,0 +1,58 @@ +- page_title milestone.title, "Milestones" + +- group = local_assigns[:group] + +.detail-page-header + .status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" } + - if milestone.closed? + Closed + - elsif milestone.expired? + Expired + - else + Open + %span.identifier + Milestone #{milestone.title} + - if milestone.expires_at + %span.creator + · + = milestone.expires_at + - if group + .pull-right + - if can?(current_user, :admin_milestones, group) + - if milestone.active? + = link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close" + - else + = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" + +.detail-page-description.gray-content-block.second-block + %h2.title + = markdown escape_once(milestone.title), pipeline: :single_line + +- if milestone.complete? && milestone.active? + .alert.alert-success.prepend-top-default + - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.' + %span All issues for this milestone are closed. #{close_msg} + +.table-holder + %table.table + %thead + %tr + %th Project + %th Open issues + %th State + %th Due date + - milestone.milestones.each do |ms| + %tr + %td + - project_name = group ? ms.project.name : ms.project.name_with_namespace + = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms) + %td + = ms.issues.opened.count + %td + - if ms.closed? + Closed + - else + Open + %td + = ms.expires_at + diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml new file mode 100644 index 00000000000..e7e04621ff4 --- /dev/null +++ b/app/views/shared/projects/_dropdown.html.haml @@ -0,0 +1,22 @@ +- @sort ||= sort_value_recently_updated +- archived = params[:archived] +.dropdown.inline + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light + = projects_sort_options_hash[@sort] + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + %li.dropdown-header + Sort by + - projects_sort_options_hash.each do |value, title| + %li + = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do + = title + + %li.divider + %li + = link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do + Hide archived projects + %li + = link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do + Show archived projects diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 99e48e86e38..97cfb76cdb0 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -28,6 +28,9 @@ = project.name .controls + - if project.main_language + %span + = project.main_language - if ci_commit %span = render_ci_status(ci_commit) |