diff --git a/CHANGELOG b/CHANGELOG index e7897a0fe776c15c5d762390c4e4c5e5095d4f9e..35c0898551dda0a6ba7bbde9a4563e9bdd53da3d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,20 +1,24 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.4.0 (unreleased) - - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu) - Implement new UI for group page - Implement search inside emoji picker - Add project permissions to all project API endpoints (Stan Hu) - Only allow group/project members to mention `@all` + - Expose Git's version in the admin area + - Add "Frequently used" category to emoji picker + - Add CAS support (tduehr) + - Add link to merge request on build detail page. -v 8.3.1 (unreleased) +v 8.3.1 - Fix Error 500 when global milestones have slashes (Stan Hu) + - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu) + - Fix LDAP identity and user retrieval when special characters are used + - Move Sidekiq-cron configuration to gitlab.yml v 8.3.0 - - Add CAS support (tduehr) - Bump rack-attack to 4.3.1 for security fix (Stan Hu) - API support for starred projects for authorized user (Zeger-Jan van de Weg) - - Add link to merge request on build detail page. - Add open_issues_count to project API (Stan Hu) - Expand character set of usernames created by Omniauth (Corey Hinshaw) - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg) @@ -74,7 +78,6 @@ v 8.3.0 - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present - Persist runners registration token in database - Fix online editor should not remove newlines at the end of the file - - Expose Git's version in the admin area v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 392440a2b009cca9a6d3ae3a1ab46a7552afd12c..04bf5cc7bb5df0f6da653857dd4d8404278068c5 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -10,6 +10,7 @@ class @AwardsHandler if $(".emoji-menu").is(":visible") $(".emoji-menu").hide() + @renderFrequentlyUsedBlock() @setupSearch() addAward: (emoji) -> @@ -20,6 +21,8 @@ class @AwardsHandler $(".emoji-menu").hide() addAwardToEmojiBar: (emoji) -> + @addEmojiToFrequentlyUsedList(emoji) + emoji = @normilizeEmojiName(emoji) if @exist(emoji) if @isActive(emoji) @@ -117,6 +120,29 @@ class @AwardsHandler normilizeEmojiName: (emoji) -> @aliases[emoji] || emoji + addEmojiToFrequentlyUsedList: (emoji) -> + frequently_used_emojis = @getFrequentlyUsedEmojis() + frequently_used_emojis.push(emoji) + $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 }) + + getFrequentlyUsedEmojis: -> + frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") + + frequently_used_emojis = ["thumbsup", "thumbsdown"].concat(frequently_used_emojis) + + _.compact(_.uniq(frequently_used_emojis)) + + renderFrequentlyUsedBlock: -> + frequently_used_emojis = @getFrequentlyUsedEmojis() + + ul = $("<ul>") + + for emoji in frequently_used_emojis + do (emoji) -> + $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul) + + $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used")) + setupSearch: -> $("input.emoji-search").keyup (ev) => term = $(ev.target).val() @@ -125,7 +151,7 @@ class @AwardsHandler $("ul.emoji-search,h5.emoji-search").remove() if term - # Generate search result block + # Generate a search result block h5 = $("<h5>").text("Search results").addClass("emoji-search") found_emojis = @searchEmojis(term).show() ul = $("<ul>").addClass("emoji-search").append(found_emojis) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 1f221945c062668955f838add7ab4c034933c681..d7a658f8faa4dd0ce23db69fef1de40d01ec137b 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,7 +1,7 @@ class @Project constructor: -> # Git protocol switcher - $('.js-protocol-switch').click -> + $('ul.clone-options-dropdown a').click -> return if $(@).hasClass('active') @@ -10,7 +10,8 @@ class @Project # Add the active class for the clicked button $(@).toggleClass('active') - url = $(@).data('clone') + url = $("#project_clone").val() + console.log("url",url) # Update the input field $('#project_clone').val(url) diff --git a/app/assets/javascripts/star.js.coffee b/app/assets/javascripts/star.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..d849b2e79500adbf730f3bee7a2ab1fb0ec18527 --- /dev/null +++ b/app/assets/javascripts/star.js.coffee @@ -0,0 +1,22 @@ +class @Star + constructor: -> + $('.project-home-panel .toggle-star').on('ajax:success', (e, data, status, xhr) -> + $this = $(this) + $starSpan = $this.find('span') + $starIcon = $this.find('i') + + toggleStar = (isStarred) -> + $this.parent().find('span.count').text data.star_count + if isStarred + $starSpan.removeClass('starred').text 'Star' + $starIcon.removeClass('fa-star').addClass 'fa-star-o' + else + $starSpan.addClass('starred').text 'Unstar' + $starIcon.removeClass('fa-star-o').addClass 'fa-star' + return + + toggleStar $starSpan.hasClass('starred') + return + ).on 'ajax:error', (e, xhr, status, error) -> + new Flash('Star toggle failed. Try again later.', 'alert') + return \ No newline at end of file diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3917efeecdbf0a4578f6f97a93e1c1c9b345b0a8..cff3edb7ed26c2a5caa0a062001c5af7d705017f 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -91,21 +91,83 @@ } } - .input-group { + .git-clone-holder { display: inline-table; position: relative; - top: 17px; } .project-repo-buttons { margin-top: 12px; margin-bottom: 0px; + .count-buttons { + display: block; + margin-bottom: 12px; + } + .btn { @include btn-gray; - + text-transform: none; + } + .count-with-arrow { + display: inline-block; + position: relative; + margin-left: 4px; + + .arrow { + &:before { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 50%; + left: 0; + margin-top: -6px; + border-width: 7px 5px 7px 0; + border-right-color: #dce0e5; + } + + &:after { + content: ''; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 50%; + left: 1px; + margin-top: -9px; + border-width: 10px 7px 10px 0; + border-right-color: #FFF; + } + } .count { + @include btn-gray; display: inline-block; + background: white; + border-radius: 2px; + border-width: 1px; + border-style: solid; + font-size: 13px; + font-weight: 600; + line-height: 20px; + padding: 11px 16px; + letter-spacing: .4px; + padding: 10px; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; + white-space: nowrap; + margin: 0 11px 0px 4px; + + &:hover { + background: #FFF; + } } } } @@ -125,6 +187,13 @@ margin-right: 45px; } + .clone-options { + display: table-cell; + a.btn { + width: 100%; + } + } + .form-control { cursor: auto; @extend .monospace; diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 682dbf2766a6306eaab85b15cb0a4a1c4bbd62c8..3004722bce033664de15775ab47227daf3e02f5b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -171,7 +171,7 @@ class ProjectsController < ApplicationController @project.reload render json: { - html: view_to_html_string("projects/buttons/_star") + star_count: @project.star_count } end diff --git a/app/models/ability.rb b/app/models/ability.rb index cd5ae0fb0fd3c99182320d605c7e24bc5904a2b9..1b3ee757040515250134b79afe752fc30883f16a 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -132,14 +132,14 @@ class Ability end def public_project_rules - project_guest_rules + [ + @public_project_rules ||= project_guest_rules + [ :download_code, :fork_project ] end def project_guest_rules - [ + @project_guest_rules ||= [ :read_project, :read_wiki, :read_issue, @@ -157,7 +157,7 @@ class Ability end def project_report_rules - project_guest_rules + [ + @project_report_rules ||= project_guest_rules + [ :create_commit_status, :read_commit_statuses, :download_code, @@ -170,7 +170,7 @@ class Ability end def project_dev_rules - project_report_rules + [ + @project_dev_rules ||= project_report_rules + [ :admin_merge_request, :create_merge_request, :create_wiki, @@ -181,7 +181,7 @@ class Ability end def project_archived_rules - [ + @project_archived_rules ||= [ :create_merge_request, :push_code, :push_code_to_protected_branches, @@ -191,7 +191,7 @@ class Ability end def project_master_rules - project_dev_rules + [ + @project_master_rules ||= project_dev_rules + [ :push_code_to_protected_branches, :update_project_snippet, :update_merge_request, @@ -206,7 +206,7 @@ class Ability end def project_admin_rules - project_master_rules + [ + @project_admin_rules ||= project_master_rules + [ :change_namespace, :change_visibility_level, :rename_project, @@ -332,7 +332,7 @@ class Ability end if snippet.public? || snippet.internal? - rules << :read_personal_snippet + rules << :read_personal_snippet end rules diff --git a/app/models/identity.rb b/app/models/identity.rb index ad60154be710f0af8092db93d76b28ad765b5f69..8bcdc1949538f9dc67a1de39223f20fc7d68f57f 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -12,6 +12,7 @@ class Identity < ActiveRecord::Base include Sortable + include CaseSensitivity belongs_to :user validates :provider, presence: true diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 76bdd68fd764b3f8a7f9f8d0f7683adb0fc06e35..b9a958fbe7be505ab4a7aa705e91beef3dc518d4 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -6,7 +6,7 @@ - else = render 'explore/head' -.gray-content-block.clearfix +.gray-content-block.clearfix.second-block = render 'filter' = render 'projects', projects: @projects = paginate @projects, theme: "gitlab" diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index e30c3633223c026e86400fa0c47a7283e63d0aa9..95d46e331f8a1d4dc41aeee78142645c349440cc 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -7,7 +7,7 @@ = render 'explore/head' .explore-trending-block - .gray-content-block + .gray-content-block.second-block .pull-right = render 'explore/projects/dropdown' .oneline diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 1412b19acde9b2fa25ea5f5596a8e965d4d106ec..fa0b718e48b7b37c6fc02b2dd347b9875a5d8264 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -7,7 +7,7 @@ = render 'explore/head' .explore-trending-block - .gray-content-block + .gray-content-block.second-block .pull-right = render 'explore/projects/dropdown' .oneline diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index c1669ac046b4207ae92914d078b0871e8e8bec0b..e92115b9b987ba29111ba28c8f6be4991c7816c6 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -27,7 +27,7 @@ = icon('rss') .project-repo-buttons - .split-one + .split-one.count-buttons = render 'projects/buttons/star' = render 'projects/buttons/fork' @@ -38,3 +38,6 @@ = render 'projects/buttons/dropdown' = render 'projects/buttons/notifications' + +:coffeescript + new Star() \ No newline at end of file diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 2d3abf09051852a7bab10bf71923ce8726e86614..133531887a202cfabb18762643723d48d437c55f 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -4,10 +4,15 @@ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do = icon('code-fork fw') Fork + %div.count-with-arrow + %span.arrow %span.count = @project.forks_count - else = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do = icon('code-fork fw') + Fork + %div.count-with-arrow + %span.arrow %span.count = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 41a3ec6d90fafbed9a389e5cd59486ad678c4837..21ba426aaa1e779a139aa29bc25ecc32be41c49f 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,19 +1,21 @@ - if current_user = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do - = icon('star fw') - %span.count + - if current_user.starred?(@project) + = icon('star fw') + %span.starred Unstar + - else + = icon('star-o fw') + %span Star + %div.count-with-arrow + %span.arrow + %span.count.star-count = @project.star_count - :javascript - $('.project-home-panel .toggle-star').on('ajax:success', function (e, data, status, xhr) { - $(this).replaceWith(data.html); - }) - .on('ajax:error', function (e, xhr, status, error) { - new Flash('Star toggle failed. Try again later.', 'alert'); - }); - - else = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do = icon('star fw') + Star + %div.count-with-arrow + %span.arrow %span.count = @project.star_count diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index edb5778f4240740ef66bbbf4560a3b0074b6e3b7..687a59c270f9f35d5b940aa740ac60b18f453839 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,10 +1,27 @@ - project = project || @project -.git-clone-holder.input-group - .input-group-addon.git-protocols - .input-group-btn - = ssh_clone_button(project) - .input-group-btn - = http_clone_button(project) + +.git-clone-holder + .btn-group.clone-options + %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'} + %span + = default_clone_protocol.upcase + = icon('angle-down') + %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown + %li + %a#ssh-selector{href: @project.ssh_url_to_repo} + SSH + %li + %a#http-selector{href: @project.http_url_to_repo} + HTTPS + = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true .input-group-btn = clipboard_button(clipboard_target: '#project_clone') + +:javascript + $('ul.clone-options-dropdown a').on('click',function(e){ + e.preventDefault(); + var $this = $(this); + $('a.clone-dropdown-btn span').text($this.text()); + $('#project_clone').val($this.attr('href')); + }); diff --git a/features/project/create.feature b/features/project/create.feature index a86079143e58ad86233cd5c37ff71de859d8def2..27136798e36d14f9b2b55a306f8b8bc30b189297 100644 --- a/features/project/create.feature +++ b/features/project/create.feature @@ -1,3 +1,4 @@ +@project-create Feature: Project Create In order to get access to project sections A user with ability to create a project diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature index 2126e826ddc0561ef5717483351a59b2fa244318..9a06fdc2ee6eb5ea0a40a84ea441a31cc3b5ebdc 100644 --- a/features/project/issues/award_emoji.feature +++ b/features/project/issues/award_emoji.feature @@ -26,5 +26,5 @@ Feature: Award Emoji @javascript Scenario: I add award emoji using regular comment - Given I leave comment with a single emoji - Then I have award added + Given I leave comment with a single emoji + Then I have award added diff --git a/features/project/star.feature b/features/project/star.feature index a45f9c470ea8921eb2f023899752af0af6e83d38..618f44fe6dc31cc2a4ce916debbcca2a85cfc233 100644 --- a/features/project/star.feature +++ b/features/project/star.feature @@ -1,3 +1,4 @@ +@project-stars Feature: Project Star Scenario: New projects have 0 stars Given public project "Community" diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb index f90218f379162380cdc78d46168dd27ca63a7b72..8a0e8fc2b6c8998937c4140558cc882e877d5a20 100644 --- a/features/steps/project/create.rb +++ b/features/steps/project/create.rb @@ -26,7 +26,8 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps end step 'I click on HTTP' do - click_button 'HTTP' + find('#clone-dropdown').click + find('#http-selector').click end step 'Remote url should update to http link' do @@ -34,7 +35,8 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps end step 'If I click on SSH' do - click_button 'SSH' + find('#clone-dropdown').click + find('#ssh-selector').click end step 'Remote url should update to ssh link' do diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb index bd2e0619cddd8be94d6717fec3d268a41313cc14..9f7c748a3b78ff3bae147189db11af8c430908e2 100644 --- a/features/steps/project/star.rb +++ b/features/steps/project/star.rb @@ -32,6 +32,6 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps protected def has_n_stars(n) - expect(page).to have_css(".star-btn .count", text: n, visible: true) + expect(page).to have_css(".star-count", text: n, visible: true) end end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 4be99dd88c29dd87ba829973da60aeca464dbf2d..aef08c97d1d7edf18a23b02f82a0eedcb5af40e1 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -14,7 +14,7 @@ module Gitlab # LDAP distinguished name is case-insensitive identity = ::Identity. where(provider: provider). - where('lower(extern_uid) = ?', uid.mb_chars.downcase.to_s).last + iwhere(extern_uid: uid).last identity && identity.user end end @@ -31,7 +31,7 @@ module Gitlab def find_by_uid_and_provider self.class.find_by_uid_and_provider( - auth_hash.uid.downcase, auth_hash.provider) + auth_hash.uid, auth_hash.provider) end def find_by_email @@ -47,7 +47,7 @@ module Gitlab # find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved. identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider } identity ||= gl_user.identities.build(provider: auth_hash.provider) - + # For a new user set extern_uid to the LDAP DN # For an existing user with matching email but changed DN, update the DN. # For an existing user with no change in DN, this line changes nothing. diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 17ce4d4b1747e7004ed7e628b5c01f85008df745..f1a362f5303a296099c4ed7c725312b9498605a1 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -64,7 +64,7 @@ module Gitlab # If a corresponding person exists with same uid in a LDAP server, # set up a Gitlab user with dual LDAP and Omniauth identities. - if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider) + if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider) # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account. user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider) else diff --git a/spec/javascripts/new_branch_spec.js.coffee b/spec/javascripts/new_branch_spec.js.coffee index 8889ce2e9b3120f368e9a259522f09f7ab999e16..f2ce85efcdcbbaac28c81ec75867d3723f587833 100644 --- a/spec/javascripts/new_branch_spec.js.coffee +++ b/spec/javascripts/new_branch_spec.js.coffee @@ -1,4 +1,4 @@ -#= require jquery.ui.all +#= require jquery-ui #= require new_branch_form describe 'Branch', -> diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 3bba5e2efa2c930c4ee406232c1e9349e72162a2..1e755259dae4a4bdb85e0f1eccd5ff8ccebefa23 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -42,6 +42,21 @@ describe Gitlab::LDAP::User, lib: true do end end + describe '.find_by_uid_and_provider' do + it 'retrieves the correct user' do + special_info = { + name: 'John Åström', + email: 'john@example.com', + nickname: 'jastrom' + } + special_hash = OmniAuth::AuthHash.new(uid: 'CN=John Åström,CN=Users,DC=Example,DC=com', provider: 'ldapmain', info: special_info) + special_chars_user = described_class.new(special_hash) + user = special_chars_user.save + + expect(described_class.find_by_uid_and_provider(special_hash.uid, special_hash.provider)).to eq user + end + end + describe :find_or_create do it "finds the user if already existing" do create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')