diff options
40 files changed, 347 insertions, 307 deletions
diff --git a/CHANGELOG b/CHANGELOG index b0540151fce..3710e8f55bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,19 @@ Please view this file on the master branch, on stable branches it's out of date. +v 8.1.0 (unreleased) + - Show CI status on all pages where commits list is rendered + - Automatically enable CI when push .gitlab-ci.yml file to repository + - Fix cases where Markdown did not render links in activity feed (Stan Hu) + - Add first and last to pagination (Zeger-Jan van de Weg) + +v 8.0.2 (unreleased) + - Skip check_initd_configured_correctly on omnibus installs + - Prevent double-prefixing of help page paths + - Clarify confirmation text on user deletion + - Make commit graphs responsive to window width changes (Stan Hu) + - Fix top margin for sign-in button on public pages + - Fix LDAP attribute mapping + v 8.0.1 - Remove git refs used internally by GitLab from network graph (Stan Hu) - Improve CI migration procedure and documentation @@ -77,7 +77,7 @@ gem "stamp", '~> 0.5.0' gem 'enumerize', '~> 0.7.0' # Pagination -gem "kaminari", "~> 0.15.1" +gem "kaminari", "~> 0.16.3" # HAML gem "haml-rails", '~> 0.5.3' diff --git a/Gemfile.lock b/Gemfile.lock index 320f7629fb6..6070beb9d07 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -367,7 +367,7 @@ GEM railties (>= 3.2.16) json (1.8.3) jwt (1.5.1) - kaminari (0.15.1) + kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) kgio (2.9.3) @@ -846,7 +846,7 @@ DEPENDENCIES jquery-scrollto-rails (~> 1.4.3) jquery-turbolinks (~> 2.0.1) jquery-ui-rails (~> 4.2.1) - kaminari (~> 0.15.1) + kaminari (~> 0.16.3) letter_opener (~> 1.1.2) mail_room (~> 0.5.1) minitest (~> 5.7.0) @@ -1 +1 @@ -8.0.0.pre +8.1.0.pre diff --git a/app/assets/javascripts/ci/pager.js.coffee b/app/assets/javascripts/ci/pager.js.coffee deleted file mode 100644 index 226fbd654ab..00000000000 --- a/app/assets/javascripts/ci/pager.js.coffee +++ /dev/null @@ -1,42 +0,0 @@ -@CiPager = - init: (@url, @limit = 0, preload, @disable = false) -> - if preload - @offset = 0 - @getItems() - else - @offset = @limit - @initLoadMore() - - getItems: -> - $(".loading").show() - $.ajax - type: "GET" - url: @url - data: "limit=" + @limit + "&offset=" + @offset - complete: => - $(".loading").hide() - success: (data) => - CiPager.append(data.count, data.html) - dataType: "json" - - append: (count, html) -> - if count > 1 - $(".content-list").append html - if count == @limit - @offset += count - else - @disable = true - - initLoadMore: -> - $(document).unbind('scroll') - $(document).endlessScroll - bottomPixels: 400 - fireDelay: 1000 - fireOnce: true - ceaseFire: -> - CiPager.disable - - callback: (i) => - unless $(".loading").is(':visible') - $(".loading").show() - CiPager.getItems() diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss index a2f6c3e21f4..421588f64d8 100644 --- a/app/assets/stylesheets/base/mixins.scss +++ b/app/assets/stylesheets/base/mixins.scss @@ -93,46 +93,87 @@ } h1 { - margin-top: 45px; - font-size: 2.5em; + font-size: 1.3em; + font-weight: 600; + margin: 24px 0 12px 0; + padding: 0 0 10px 0; + border-bottom: 1px solid #e7e9ed; + color: #313236; } h2 { - margin-top: 40px; - font-size: 2em; + font-size: 1.2em; + font-weight: 600; + margin: 24px 0 12px 0; + color: #313236; } h3 { - margin-top: 35px; - font-size: 1.5em; + margin: 24px 0 12px 0; + font-size: 1.25em; } h4 { - margin-top: 30px; - font-size: 1.2em; + margin: 24px 0 12px 0; + font-size: 1.1em; + } + + h5 { + margin: 24px 0 12px 0; + font-size: 1em; + } + + h6 { + margin: 24px 0 12px 0; + font-size: 0.90em; } blockquote { - color: #888; + padding: 8px 21px; + margin: 12px 0 12px; + border-left: 3px solid #e7e9ed; + } + + blockquote p { + color: #7f8fa4 !important; font-size: 15px; line-height: 1.5; } - + + p { + color:#5c5d5e; + margin:6px 0 0 0; + } + table { @extend .table; @extend .table-bordered; + margin: 12px 0 12px 0; + color: #5c5d5e; th { - background: #EEE; - } + background: #f8fafc; + } + } + + pre { + margin: 12px 0 12px 0 !important; + background-color: #f8fafc !important; + font-size: 13px !important; + color: #5b6169 !important; + line-height: 1.6em !important; } p > code { - font-size: inherit; font-weight: inherit; } + + ul { + color: #5c5d5e; + } + li { - line-height: 1.5; + line-height: 1.6em; } a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] { @@ -152,6 +193,7 @@ } } + @mixin str-truncated($max_width: 82%) { display: inline-block; overflow: hidden; @@ -183,7 +225,7 @@ &.active { background: #f9f9f9; a { - font-weight: bold; + font-weight: 600; } } diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 48fad7701ef..b659717b4e1 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -302,7 +302,7 @@ table { } .btn-sign-in { - margin-top: 15px; + margin-top: 8px; text-shadow: none; } diff --git a/app/assets/stylesheets/generic/pagination.scss b/app/assets/stylesheets/generic/pagination.scss index a937677ebdc..6677f94dafd 100644 --- a/app/assets/stylesheets/generic/pagination.scss +++ b/app/assets/stylesheets/generic/pagination.scss @@ -9,6 +9,8 @@ margin: 0; display: block; + li.first, + li.last, li.next, li.prev { > a { diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index d5f0d86a307..7a8a17ced99 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -55,6 +55,7 @@ a > code { @include md-typography; word-wrap: break-word; + padding: 7px; /* Link to current header. */ h1, h2, h3, h4, h5, h6 { @@ -83,9 +84,12 @@ a > code { } } - ul { + ul,ol { padding: 0; - margin: 0 0 9px 25px !important; + margin: 6px 0 6px 18px !important; + } + ol { + color: #5c5d5e; } } diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index 40b61edb0a9..250a2e79313 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -1,12 +1,10 @@ module Ci class ProjectsController < Ci::ApplicationController - PROJECTS_BATCH = 100 - before_action :authenticate_user!, except: [:build, :badge, :index, :show] before_action :authenticate_public_page!, only: :show - before_action :project, only: [:build, :integration, :show, :badge, :edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml] - before_action :authorize_access_project!, except: [:build, :badge, :index, :show, :new, :create, :disabled] - before_action :authorize_manage_project!, only: [:edit, :integration, :update, :destroy, :toggle_shared_runners, :dumped_yaml] + before_action :project, only: [:build, :show, :badge, :edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml] + before_action :authorize_access_project!, except: [:build, :badge, :index, :show, :new, :disabled] + before_action :authorize_manage_project!, only: [:edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml] before_action :authenticate_token!, only: [:build] before_action :no_cache, only: [:badge] skip_before_action :check_enable_flag!, only: [:disabled] @@ -18,23 +16,15 @@ module Ci end def index - @limit, @offset = (params[:limit] || PROJECTS_BATCH).to_i, (params[:offset] || 0).to_i - @page = @offset == 0 ? 1 : (@offset / @limit + 1) + @projects = Ci::Project.all if current_user - @projects = ProjectListBuilder.new.execute(current_user, params[:search]) - - @projects = @projects.page(@page).per(@limit) - - @total_count = @projects.size + @projects = @projects.where(gitlab_id: current_user.authorized_projects.pluck(:id)) end - respond_to do |format| - format.json do - pager_json("ci/projects/index", @total_count) - end - format.html - end + @projects = @projects.search(params[:search]) if params[:search].present? + @projects = @projects.includes(:last_commit).order('ci_commits.created_at DESC') + @projects = @projects.page(params[:page]).per(40) end def show @@ -45,25 +35,6 @@ module Ci @commits = @commits.page(params[:page]).per(20) end - def integration - end - - def create - project_data = OpenStruct.new(JSON.parse(params["project"])) - - unless can?(current_user, :admin_project, ::Project.find(project_data.id)) - return redirect_to ci_root_path, alert: 'You have to have at least master role to enable CI for this project' - end - - @project = Ci::CreateProjectService.new.execute(current_user, project_data) - - if @project.persisted? - redirect_to ci_project_path(@project, show_guide: true), notice: 'Project was successfully created.' - else - redirect_to :back, alert: 'Cannot save project' - end - end - def edit end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index ad00948da51..55050615473 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -4,6 +4,11 @@ class HelpController < ApplicationController layout 'help' def index + @help_index = File.read(Rails.root.join('doc', 'README.md')) + + # Prefix Markdown links with `help/` unless they already have been + # See http://rubular.com/r/nwwhzH6Z8X + @help_index.gsub!(/(\]\()(?!help\/)([^\)\(]+)(\))/, '\1help/\2\3') end def show diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index c1231994f25..7574842cd43 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -258,8 +258,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commits = @merge_request.commits @merge_request_diff = @merge_request.merge_request_diff - @source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name) - + if @merge_request.locked_long_ago? @merge_request.unlock_mr @merge_request.close diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb new file mode 100644 index 00000000000..18c30ddb281 --- /dev/null +++ b/app/helpers/ci_status_helper.rb @@ -0,0 +1,34 @@ +module CiStatusHelper + def ci_status_path(ci_commit) + ci_project_ref_commits_path(ci_commit.project, ci_commit.ref, ci_commit) + end + + def ci_status_icon(ci_commit) + icon_name = + case ci_commit.status + when 'success' + 'check' + when 'failed' + 'close' + when 'running', 'pending' + 'clock-o' + else + 'circle' + end + + icon(icon_name) + end + + def ci_status_color(ci_commit) + case ci_commit.status + when 'success' + 'green' + when 'failed' + 'red' + when 'running', 'pending' + 'yellow' + else + 'gray' + end + end +end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index d13d80be293..9df20c9fce5 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -135,7 +135,7 @@ module CommitsHelper # size: size of the avatar image in px def commit_person_link(commit, options = {}) user = commit.send(options[:source]) - + source_name = clean(commit.send "#{options[:source]}_name".to_sym) source_email = clean(commit.send "#{options[:source]}_email".to_sym) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 78bf25f55e7..153a44870f6 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -165,7 +165,7 @@ module GitlabMarkdownHelper # and return true. Otherwise return false. def truncate_if_block(node, truncated) if node.element? && node.description.block? && !truncated - node.content = "#{node.content}..." if node.next_sibling + node.inner_html = "#{node.inner_html}..." if node.next_sibling true else truncated diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 37fbcc287bb..a52e28615f7 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -41,6 +41,7 @@ module Ci has_many :events, dependent: :destroy, class_name: 'Ci::Event' has_many :variables, dependent: :destroy, class_name: 'Ci::Variable' has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger' + has_one :last_commit, -> { order 'ci_commits.created_at DESC' }, class_name: 'Ci::Commit' # Project services has_many :services, dependent: :destroy, class_name: 'Ci::Service' diff --git a/app/models/project.rb b/app/models/project.rb index 1a5c1c978c9..a7ea1236b86 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -735,4 +735,18 @@ class Project < ActiveRecord::Base errors.add(:base, 'Failed create wiki') false end + + def ci_commit(sha) + gitlab_ci_project.commits.find_by(sha: sha) if gitlab_ci? + end + + def enable_ci(user) + # Enable service + service = gitlab_ci_service || create_gitlab_ci_service + service.active = true + service.save + + # Create Ci::Project + Ci::CreateProjectService.new.execute(user, self) + end end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 9e2b3bcd873..ec3bd44024f 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -72,7 +72,7 @@ class GitlabCiService < CiService }) ci_project = Ci::Project.find_by!(gitlab_id: project.id) - + Ci::CreateProjectService.new.execute( current_user, params, diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 0a73244774a..8193b6e192d 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -55,6 +55,12 @@ class GitPushService @push_data = build_push_data(oldrev, newrev, ref) + # If CI was disabled but .gitlab-ci.yml file was pushed + # we enable CI automatically + if !project.gitlab_ci? && gitlab_ci_yaml?(newrev) + project.enable_ci(user) + end + EventCreateService.new.push(project, user, @push_data) project.execute_hooks(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks) @@ -143,4 +149,10 @@ class GitPushService def commit_user(commit) commit.author || user end + + def gitlab_ci_yaml?(sha) + @project.repository.blob_at(sha, '.gitlab-ci.yml') + rescue Rugged::ReferenceError + nil + end end diff --git a/app/views/ci/projects/_project.html.haml b/app/views/ci/projects/_project.html.haml index 844b6677b3d..58022de9bc1 100644 --- a/app/views/ci/projects/_project.html.haml +++ b/app/views/ci/projects/_project.html.haml @@ -1,37 +1,24 @@ -- if project.gitlab_ci_project - - ci_project = project.gitlab_ci_project - - last_commit = ci_project.last_commit - %tr - %td - = link_to [:ci, ci_project] do - = ci_project.name - %td - - if last_commit - = ci_status_with_icon(last_commit.status) - = commit_link(last_commit) - · - - if ci_project.last_commit_date - = time_ago_in_words ci_project.last_commit_date - ago - - else - No builds yet - %td - - if ci_project.public - %i.fa.fa-globe - Public - - else - %i.fa.fa-lock - Private - %td - = ci_project.commits.count -- else - %tr.light - %td - = project.name_with_namespace - %td - %small Not added to CI - %td - %td - = form_tag ci_projects_path do - = hidden_field_tag :project, project.to_json(methods: [:name_with_namespace, :path_with_namespace, :ssh_url_to_repo]) - = submit_tag 'Add project to CI', class: 'btn btn-default btn-sm' +- last_commit = project.last_commit +%tr + %td + = link_to [:ci, project] do + = project.name + %td + - if last_commit + = ci_status_with_icon(last_commit.status) + = commit_link(last_commit) + · + - if project.last_commit_date + = time_ago_in_words project.last_commit_date + ago + - else + No builds yet + %td + - if project.public + %i.fa.fa-globe + Public + - else + %i.fa.fa-lock + Private + %td + = project.commits.count diff --git a/app/views/ci/projects/_search.html.haml b/app/views/ci/projects/_search.html.haml index 4ab43a403f7..a956ed4c0bc 100644 --- a/app/views/ci/projects/_search.html.haml +++ b/app/views/ci/projects/_search.html.haml @@ -1,11 +1,6 @@ .search - = form_tag "#", method: :get, class: 'ci-search-form' do |f| + = form_tag ci_root_path, method: :get, class: 'ci-search-form' do |f| .input-group = search_field_tag "search", params[:search], placeholder: "Search", class: "search-input form-control" .input-group-addon %i.fa.fa-search - -:coffeescript - $('.ci-search-form').submit -> - CiPager.init "#{ci_projects_path}" + "?search=" + query, #{Ci::ProjectsController::PROJECTS_BATCH}, false - false diff --git a/app/views/ci/projects/index.html.haml b/app/views/ci/projects/index.html.haml index 2b618d61f79..046095a3c12 100644 --- a/app/views/ci/projects/index.html.haml +++ b/app/views/ci/projects/index.html.haml @@ -1,30 +1,17 @@ - if current_user - - if @offset > 0 - = render @projects - - else - .gray-content-block.top-block - = render "search" - .projects - .gray-content-block.clearfix.light.second-block - .pull-left.fetch-status - - if params[:search].present? - by keyword: "#{params[:search]}", - #{@total_count} projects - - .wide-table-holder - %table.table.projects-table.content-list - %thead - %tr - %th Project Name - %th Last commit - %th Access - %th Commits - - = render @projects - %p.text-center.hide.loading - %i.fa.fa-refresh.fa-spin - :coffeescript - CiPager.init "#{ci_projects_path}", #{Ci::ProjectsController::PROJECTS_BATCH}, false + .gray-content-block.top-block + = render "search" + .projects + .wide-table-holder + %table.table.projects-table.content-list + %thead + %tr + %th Project Name + %th Last commit + %th Access + %th Commits + = render @projects + = paginate @projects, theme: 'gitlab' - else = render 'public' diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index f492aaf4c0a..ab7ed1b5d95 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -27,11 +27,7 @@ .col-md-8 .documentation-index = preserve do - - readme_text = File.read(Rails.root.join("doc", "README.md")) - - text = readme_text.dup - - readme_text.scan(/\]\(([^(]+)\)/) { |match| text.gsub!(match.first, "help/#{match.first}") } - = markdown text - + = markdown(@help_index) .col-md-4 .panel.panel-default .panel-heading diff --git a/app/views/kaminari/gitlab/_first_page.html.haml b/app/views/kaminari/gitlab/_first_page.html.haml index 41c9c0b3af6..ada7306d98d 100644 --- a/app/views/kaminari/gitlab/_first_page.html.haml +++ b/app/views/kaminari/gitlab/_first_page.html.haml @@ -5,5 +5,5 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.first +%li.first = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote diff --git a/app/views/kaminari/gitlab/_last_page.html.haml b/app/views/kaminari/gitlab/_last_page.html.haml index b03a206224c..3431d029bcc 100644 --- a/app/views/kaminari/gitlab/_last_page.html.haml +++ b/app/views/kaminari/gitlab/_last_page.html.haml @@ -5,5 +5,5 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.last +%li.last = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote} diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml index b8d419b5894..2f645186921 100644 --- a/app/views/kaminari/gitlab/_paginator.html.haml +++ b/app/views/kaminari/gitlab/_paginator.html.haml @@ -8,10 +8,15 @@ = paginator.render do %div.gl-pagination %ul.pagination.clearfix - = prev_page_tag unless current_page.first? + - unless current_page.first? + = first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages + = prev_page_tag - each_page do |page| - if page.left_outer? || page.right_outer? || page.inside_window? = page_tag page - elsif !page.was_truncated? = gap_tag - = next_page_tag unless current_page.last? + - unless current_page.last? + = next_page_tag + = last_page_tag unless num_pages < 5 + diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 74f8d8b15cf..efad4cb1473 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -4,7 +4,11 @@ - notes = commit.notes - note_count = notes.user.count -= cache [project.id, commit.id, note_count] do +- ci_commit = project.ci_commit(commit.sha) +- cache_key = [project.id, commit.id, note_count] +- cache_key.push(ci_commit.status) if ci_commit + += cache(cache_key) do %li.commit.js-toggle-container .commit-row-title %strong.str-truncated @@ -13,6 +17,10 @@ %a.text-expander.js-toggle-button ... .pull-right + - if ci_commit + = link_to ci_status_path(ci_commit), class: "c#{ci_status_color(ci_commit)}" do + = ci_status_icon(ci_commit) + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" .notes_count diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index bf0cac539b8..112be875b6b 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -32,61 +32,55 @@ %div %p.slead Commits per day of month - %canvas#month-chart{width: 800, height: 400} + %canvas#month-chart .row .col-md-6 %div %p.slead Commits per day hour (UTC) - %canvas#hour-chart{width: 800, height: 400} + %canvas#hour-chart .col-md-6 %div %p.slead Commits per weekday - %canvas#weekday-chart{width: 800, height: 400} + %canvas#weekday-chart :coffeescript - data = { - labels : #{@commits_per_time.keys.to_json}, - datasets : [{ - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data : #{@commits_per_time.values.to_json} - }] - } + responsiveChart = (selector, data) -> + options = { "scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2, maintainAspectRatio: false } - ctx = $("#hour-chart").get(0).getContext("2d"); - new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + # get selector by context + ctx = selector.get(0).getContext("2d") + # pointing parent container to make chart.js inherit its width + container = $(selector).parent() - data = { - labels : #{@commits_per_week_days.keys.to_json}, - datasets : [{ - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data : #{@commits_per_week_days.values.to_json} - }] - } + generateChart = -> + selector.attr('width', $(container).width()) + new Chart(ctx).Bar(data, options) + + # enabling auto-resizing + $(window).resize( generateChart ) - ctx = $("#weekday-chart").get(0).getContext("2d"); - new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + generateChart() - data = { - labels : #{@commits_per_month.keys.to_json}, - datasets : [{ - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data : #{@commits_per_month.values.to_json} - }] + chartData = (keys, values) -> + data = { + labels : keys, + datasets : [{ + fillColor : "rgba(220,220,220,0.5)", + strokeColor : "rgba(220,220,220,1)", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data : values + }] } - ctx = $("#month-chart").get(0).getContext("2d"); - new Chart(ctx).Bar(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + hourData = chartData(#{@commits_per_time.keys.to_json}, #{@commits_per_time.values.to_json}) + responsiveChart($('#hour-chart'), hourData) + + dayData = chartData(#{@commits_per_week_days.keys.to_json}, #{@commits_per_week_days.values.to_json}) + responsiveChart($('#weekday-chart'), dayData) + + monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json}) + responsiveChart($('#month-chart'), monthData) diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml index d22dfa085b8..f223f687def 100644 --- a/app/views/projects/merge_requests/widget/_merged.html.haml +++ b/app/views/projects/merge_requests/widget/_merged.html.haml @@ -20,7 +20,7 @@ The changes were merged into %span.label-branch= @merge_request.target_branch You can remove the source branch now. - = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do + = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do %i.fa.fa-times Remove Source Branch diff --git a/doc/README.md b/doc/README.md index 5f38086b8e3..a0ff856ebb6 100644 --- a/doc/README.md +++ b/doc/README.md @@ -51,7 +51,7 @@ ### Administrator documentation -+ [User permissions](permissions/README.md) ++ [User permissions](permissions/permissions.md) + [API](api/README.md) ## Contributor documentation diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 6fdb2fe1491..d83838127c9 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -33,7 +33,6 @@ For GitLab we developed something we call "GitLab Flavored Markdown" (GFM). It e You can use GFM in -- commit messages - comments - issues - merge requests diff --git a/lib/api/users.rb b/lib/api/users.rb index 813cc379e43..a98d668e02d 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -121,6 +121,17 @@ module API User.where(username: attrs[:username]). where.not(id: user.id).count > 0 + identity_attrs = attributes_for_keys [:provider, :extern_uid] + if identity_attrs.any? + identity = user.identities.find_by(provider: identity_attrs[:provider]) + if identity + identity.update_attributes(identity_attrs) + else + identity = user.identities.build(identity_attrs) + identity.save + end + end + if user.update_attributes(attrs) present user, with: Entities::UserFull else diff --git a/lib/ci/project_list_builder.rb b/lib/ci/project_list_builder.rb deleted file mode 100644 index da26f9a9f47..00000000000 --- a/lib/ci/project_list_builder.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Ci - class ProjectListBuilder - def execute(current_user, search = nil) - projects = current_user.authorized_projects - projects = projects.search(search) if search - - projects. - joins("LEFT JOIN ci_projects ON projects.id = ci_projects.gitlab_id - LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.id = last_commit.project_id"). - reorder("ci_projects.id is NULL ASC, - CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, - last_commit.committed_at DESC") - end - - private - - def last_commit_subquery - "(SELECT project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY project_id)" - end - end -end diff --git a/lib/gitlab/ldap/auth_hash.rb b/lib/gitlab/ldap/auth_hash.rb index 55deeeacd90..bf4dd9542d5 100644 --- a/lib/gitlab/ldap/auth_hash.rb +++ b/lib/gitlab/ldap/auth_hash.rb @@ -6,7 +6,7 @@ module Gitlab private def get_info(key) - attributes = ldap_config.attributes[key] + attributes = ldap_config.attributes[key.to_s] return super unless attributes attributes = Array(attributes) @@ -14,6 +14,7 @@ module Gitlab value = nil attributes.each do |attribute| value = get_raw(attribute) + value = value.first if value break if value.present? end diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb deleted file mode 100644 index 3e579f9a7d6..00000000000 --- a/spec/controllers/ci/projects_controller_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require "spec_helper" - -describe Ci::ProjectsController do - before do - @project = FactoryGirl.create :ci_project - end - - describe "POST /projects" do - let(:project_dump) { OpenStruct.new({ id: @project.gitlab_id }) } - - let(:user) do - create(:user) - end - - before do - sign_in(user) - end - - it "creates project" do - post :create, { project: JSON.dump(project_dump.to_h) }.with_indifferent_access - - expect(response.code).to eq('302') - expect(assigns(:project)).not_to be_a_new(Ci::Project) - end - - it "shows error" do - post :create, { project: JSON.dump(project_dump.to_h) }.with_indifferent_access - - expect(response.code).to eq('302') - expect(flash[:alert]).to include("You have to have at least master role to enable CI for this project") - end - end - - describe "GET /gitlab" do - let(:user) do - create(:user) - end - - before do - sign_in(user) - end - - it "searches projects" do - xhr :get, :index, { search: "str", format: "json" }.with_indifferent_access - - expect(response).to be_success - expect(response.code).to eq('200') - end - end -end diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb new file mode 100644 index 00000000000..7fc53eb1472 --- /dev/null +++ b/spec/helpers/ci_status_helper_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe CiStatusHelper do + include IconsHelper + + let(:success_commit) { double("Ci::Commit", status: 'success') } + let(:failed_commit) { double("Ci::Commit", status: 'failed') } + + describe 'ci_status_color' do + it { expect(ci_status_icon(success_commit)).to include('fa-check') } + it { expect(ci_status_icon(failed_commit)).to include('fa-close') } + end + + describe 'ci_status_color' do + it { expect(ci_status_color(success_commit)).to eq('green') } + it { expect(ci_status_color(failed_commit)).to eq('red') } + end +end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index b8101ae77ec..be0e0c747b7 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -146,4 +146,24 @@ describe GitlabMarkdownHelper do expect(random_markdown_tip).to eq 'Random tip' end end + + describe '#first_line_in_markdown' do + let(:text) { "@#{user.username}, can you look at this?\nHello world\n"} + + it 'truncates Markdown properly' do + actual = first_line_in_markdown(text, 100, project: project) + + doc = Nokogiri::HTML.parse(actual) + + # Make sure we didn't create invalid markup + expect(doc.errors).to be_empty + + # Leading user link + expect(doc.css('a').length).to eq(1) + expect(doc.css('a')[0].attr('href')).to eq user_path(user) + expect(doc.css('a')[0].text).to eq "@#{user.username}" + + expect(doc.content).to eq "@#{user.username}, can you look at this?..." + end + end end diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb index 18c7924fea1..7d8268536a4 100644 --- a/spec/lib/gitlab/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb @@ -24,10 +24,10 @@ describe Gitlab::LDAP::AuthHash do let(:raw_info) do { - uid: '123456', - email: 'johnsmith@example.com', - cn: 'Smith, J.', - fullName: 'John Smith' + uid: ['123456'], + email: ['johnsmith@example.com'], + cn: ['Smith, J.'], + fullName: ['John Smith'] } end @@ -45,8 +45,8 @@ describe Gitlab::LDAP::AuthHash do context "with overridden attributes" do let(:attributes) do { - username: ['mail', 'email'], - name: 'fullName' + 'username' => ['mail', 'email'], + 'name' => 'fullName' } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2fcbd5ae108..9e7b6f5cb30 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -401,4 +401,24 @@ describe Project do it { should eq "http://localhost#{avatar_path}" } end end + + describe :ci_commit do + let(:project) { create :project } + let(:ci_project) { create :ci_project, gl_project: project } + let(:commit) { create :ci_commit, project: ci_project } + + before { project.create_gitlab_ci_service(active: true) } + + it { expect(project.ci_commit(commit.sha)).to eq(commit) } + end + + describe :enable_ci do + let(:project) { create :project } + let(:user) { create :user } + + before { project.enable_ci(user) } + + it { expect(project.gitlab_ci?).to be_truthy } + it { expect(project.gitlab_ci_project).to be_a(Ci::Project) } + end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f9bc63680ba..d26a300ed82 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -7,6 +7,7 @@ describe API::API, api: true do let(:admin) { create(:admin) } let(:key) { create(:key, user: user) } let(:email) { create(:email, user: user) } + let(:omniauth_user) { create(:omniauth_user) } describe "GET /users" do context "when unauthenticated" do @@ -230,6 +231,19 @@ describe API::API, api: true do expect(user.reload.username).to eq(user.username) end + it "should update user's existing identity" do + put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321' + expect(response.status).to eq(200) + expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321') + end + + it 'should update user with new identity' do + put api("/users/#{user.id}", admin), provider: 'github', extern_uid: '67890' + expect(response.status).to eq(200) + expect(user.reload.identities.first.extern_uid).to eq('67890') + expect(user.reload.identities.first.provider).to eq('github') + end + it "should update admin status" do put api("/users/#{user.id}", admin), { admin: true } expect(response.status).to eq(200) |