From 11dda8db29a4843026464c0a61f65ada20646e3b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 4 Jan 2016 14:51:59 +0100 Subject: [PATCH 001/153] Add note in upgrade guide to choose the correct version tag [ci skip] --- doc/update/8.2-to-8.3.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/update/8.2-to-8.3.md b/doc/update/8.2-to-8.3.md index 3748941b78..e028975d4e 100644 --- a/doc/update/8.2-to-8.3.md +++ b/doc/update/8.2-to-8.3.md @@ -1,5 +1,14 @@ # From 8.2 to 8.3 +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + **NOTE:** GitLab 8.0 introduced several significant changes related to installation and configuration which *are not duplicated here*. Be sure you're already running a working version of at least 8.0 before proceeding with this From 3d99ffc862b76c4dcc560aa8d8f4f9c1b7dd3f81 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 2 Mar 2016 11:33:55 +0000 Subject: [PATCH 002/153] Edited timeago text on comments Unified the 'edited text' to be the same in descriptions and comments Closes #5538 --- app/assets/javascripts/notes.js.coffee | 1 + app/assets/stylesheets/pages/issuable.scss | 8 +++++ app/helpers/application_helper.rb | 30 ++++++++++++++----- app/views/projects/issues/show.html.haml | 5 +--- .../merge_requests/show/_mr_box.html.haml | 5 +--- app/views/projects/notes/_note.html.haml | 10 +------ 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index c95ead22e6..063304ce89 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -328,6 +328,7 @@ class @Notes updateNote: (_xhr, note, _status) => # Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html) + $('.js-timeago', $html).timeago() $html.syntaxHighlight() $html.find('.js-task-list-container').taskList('enable') diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 1310e6ad7c..e417869cc0 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -262,3 +262,11 @@ color: $gray-darkest; } } + +.edited-text { + color: $gray-darkest; + + .author_link { + color: $gray-darkest; + } +} diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 368969c647..cc4d2a8877 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -182,20 +182,36 @@ module ApplicationHelper # Returns an HTML-safe String def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) element = content_tag :time, time.to_s, - class: "#{html_class} js-timeago js-timeago-pending", + class: "#{html_class} js-timeago", datetime: time.to_time.getutc.iso8601, title: time.in_time_zone.to_s(:medium), data: { toggle: 'tooltip', placement: placement, container: 'body' } - unless skip_js - element << javascript_tag( - "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()" - ) - end - element end + def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', skip_js: false, include_author: false) + return nil if object.updated_at == object.created_at + + content_tag :small, class: "edited-text" do + output = content_tag :span do + "Edited " + end + output += time_ago_with_tooltip(object.updated_at) + + if include_author + if object.updated_by && object.updated_by != object.author + output += content_tag :span do + " by " + end + output += link_to_member(object.project, object.updated_by, avatar: false, author_class: nil) + end + end + + output + end + end + def render_markup(file_name, file_content) if gitlab_markdown?(file_name) Haml::Helpers.preserve(markdown(file_content)) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 617b043780..f507fd7f5b 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -63,10 +63,7 @@ = markdown(@issue.description, cache_key: [@issue, "description"]) %textarea.hidden.js-task-list-field = @issue.description - - if @issue.updated_at != @issue.created_at - %small - Edited - = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago') + = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') .merge-requests = render 'merge_requests' diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index 602f787e6c..a23bd8d18d 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -11,7 +11,4 @@ %textarea.hidden.js-task-list-field = @merge_request.description - - if @merge_request.updated_at != @merge_request.created_at - %small - Edited - = time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom') + = edited_time_ago_with_tooltip(@merge_request, placement: 'bottom') diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 52972576af..2cf32e6093 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -27,20 +27,13 @@ %span.note-last-update %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'} = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago') - - if note.updated_at != note.created_at - %span.note-updated-at - · - = icon('edit', title: 'edited') - = time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago') - - if note.updated_by && note.updated_by != note.author - by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)} - .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-text = preserve do = markdown(note.note, pipeline: :note, cache_key: [note, "note"]) - if note_editable?(note) = render 'projects/notes/edit_form', note: note + = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note.attachment.url .note-attachment @@ -54,4 +47,3 @@ = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note), title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do = icon('trash-o', class: 'cred') - .clear From 538f3e0d716d0f8c107af030fb318808f9e284ac Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 3 Mar 2016 15:07:45 +0000 Subject: [PATCH 003/153] Fixed ruby issues from feedback Fixed failing tests --- app/helpers/application_helper.rb | 19 +++++++------------ app/views/projects/_last_commit.html.haml | 2 +- app/views/projects/blame/show.html.haml | 2 +- app/views/projects/commits/_commit.html.haml | 2 +- spec/helpers/application_helper_spec.rb | 15 ++------------- 5 files changed, 12 insertions(+), 28 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cc4d2a8877..f99a14c0e0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -168,7 +168,6 @@ module ApplicationHelper # time - Time object # placement - Tooltip placement String (default: "top") # html_class - Custom class for `time` element (default: "time_ago") - # skip_js - When true, exclude the `script` tag (default: false) # # By default also includes a `script` element with Javascript necessary to # initialize the `timeago` jQuery extension. If this method is called many @@ -180,7 +179,7 @@ module ApplicationHelper # `html_class` argument is provided. # # Returns an HTML-safe String - def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) + def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago') element = content_tag :time, time.to_s, class: "#{html_class} js-timeago", datetime: time.to_time.getutc.iso8601, @@ -190,21 +189,17 @@ module ApplicationHelper element end - def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', skip_js: false, include_author: false) - return nil if object.updated_at == object.created_at + def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', include_author: false) + return if object.updated_at == object.created_at content_tag :small, class: "edited-text" do - output = content_tag :span do - "Edited " - end - output += time_ago_with_tooltip(object.updated_at) + output = content_tag(:span, "Edited ") + output << time_ago_with_tooltip(object.updated_at, placement: placement, html_class: html_class) if include_author if object.updated_by && object.updated_by != object.author - output += content_tag :span do - " by " - end - output += link_to_member(object.project, object.updated_by, avatar: false, author_class: nil) + output << content_tag(:span, " by ") + output << link_to_member(object.project, object.updated_by, avatar: false, author_class: nil) end end diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 386d72e778..a47aea7ff1 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -8,5 +8,5 @@ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" · - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by + #{time_ago_with_tooltip(commit.committed_date)} by = commit_author_link(commit, avatar: true, size: 24) diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 5f9a92ff93..1ae83c2383 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -29,7 +29,7 @@ .light = commit_author_link(commit, avatar: false) authored - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} + #{time_ago_with_tooltip(commit.committed_date)} %td.line-numbers - line_count = blame_group[:lines].count - (current_line...(current_line + line_count)).each do |i| diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7f2903589a..4df4a8ca9e 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -38,5 +38,5 @@ = commit_author_link(commit, avatar: true, size: 24) authored .committed_ago - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}   + #{time_ago_with_tooltip(commit.committed_date)}   = link_to_browse_code(project, commit) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 8013b31524..2c27e6a031 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -263,29 +263,18 @@ describe ApplicationHelper do end it 'includes a default js-timeago class' do - expect(element.attr('class')).to eq 'time_ago js-timeago js-timeago-pending' + expect(element.attr('class')).to eq 'time_ago js-timeago' end it 'accepts a custom html_class' do expect(element(html_class: 'custom_class').attr('class')). - to eq 'custom_class js-timeago js-timeago-pending' + to eq 'custom_class js-timeago' end it 'accepts a custom tooltip placement' do expect(element(placement: 'bottom').attr('data-placement')).to eq 'bottom' end - it 're-initializes timeago Javascript' do - el = element.next_element - - expect(el.name).to eq 'script' - expect(el.text).to include "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()" - end - - it 'allows the script tag to be excluded' do - expect(element(skip_js: true)).not_to include 'script' - end - it 'converts to Time' do expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error end From 12506abfe794e75fcaa879b92bee09b8da57bf46 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 3 Mar 2016 17:04:32 +0000 Subject: [PATCH 004/153] Single if statement --- app/helpers/application_helper.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f99a14c0e0..212fecc3e7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -196,11 +196,9 @@ module ApplicationHelper output = content_tag(:span, "Edited ") output << time_ago_with_tooltip(object.updated_at, placement: placement, html_class: html_class) - if include_author - if object.updated_by && object.updated_by != object.author - output << content_tag(:span, " by ") - output << link_to_member(object.project, object.updated_by, avatar: false, author_class: nil) - end + if include_author && object.updated_by && object.updated_by != object.author + output << content_tag(:span, " by ") + output << link_to_member(object.project, object.updated_by, avatar: false, author_class: nil) end output From cd1d045fe215bb06a1cc96e12cefda5ef71058a0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 9 Mar 2016 16:29:15 +0000 Subject: [PATCH 005/153] Fixed issue with timeago not firing --- app/helpers/application_helper.rb | 10 ++++++++-- app/views/projects/_last_commit.html.haml | 2 +- app/views/projects/blame/show.html.haml | 2 +- app/views/projects/commits/_commit.html.haml | 2 +- spec/helpers/application_helper_spec.rb | 15 +++++++++++++-- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 212fecc3e7..5cd494079b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -179,13 +179,19 @@ module ApplicationHelper # `html_class` argument is provided. # # Returns an HTML-safe String - def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago') + def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) element = content_tag :time, time.to_s, - class: "#{html_class} js-timeago", + class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}", datetime: time.to_time.getutc.iso8601, title: time.in_time_zone.to_s(:medium), data: { toggle: 'tooltip', placement: placement, container: 'body' } + unless skip_js + element << javascript_tag( + "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()" + ) + end + element end diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index a47aea7ff1..386d72e778 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -8,5 +8,5 @@ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" · - #{time_ago_with_tooltip(commit.committed_date)} by + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by = commit_author_link(commit, avatar: true, size: 24) diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 1ae83c2383..5f9a92ff93 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -29,7 +29,7 @@ .light = commit_author_link(commit, avatar: false) authored - #{time_ago_with_tooltip(commit.committed_date)} + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} %td.line-numbers - line_count = blame_group[:lines].count - (current_line...(current_line + line_count)).each do |i| diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 4df4a8ca9e..7f2903589a 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -38,5 +38,5 @@ = commit_author_link(commit, avatar: true, size: 24) authored .committed_ago - #{time_ago_with_tooltip(commit.committed_date)}   + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}   = link_to_browse_code(project, commit) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 2c27e6a031..8013b31524 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -263,18 +263,29 @@ describe ApplicationHelper do end it 'includes a default js-timeago class' do - expect(element.attr('class')).to eq 'time_ago js-timeago' + expect(element.attr('class')).to eq 'time_ago js-timeago js-timeago-pending' end it 'accepts a custom html_class' do expect(element(html_class: 'custom_class').attr('class')). - to eq 'custom_class js-timeago' + to eq 'custom_class js-timeago js-timeago-pending' end it 'accepts a custom tooltip placement' do expect(element(placement: 'bottom').attr('data-placement')).to eq 'bottom' end + it 're-initializes timeago Javascript' do + el = element.next_element + + expect(el.name).to eq 'script' + expect(el.text).to include "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()" + end + + it 'allows the script tag to be excluded' do + expect(element(skip_js: true)).not_to include 'script' + end + it 'converts to Time' do expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error end From c7730121c16e41d797e7f7e00f7a6ad96d78bbb4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 10 Mar 2016 08:39:32 +0000 Subject: [PATCH 006/153] Fixed failing rubocop test --- app/helpers/application_helper.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5cd494079b..f07c79ec61 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -186,11 +186,11 @@ module ApplicationHelper title: time.in_time_zone.to_s(:medium), data: { toggle: 'tooltip', placement: placement, container: 'body' } - unless skip_js - element << javascript_tag( - "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()" - ) - end + unless skip_js + element << javascript_tag( + "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()" + ) + end element end From 20c663e182f0c54e1cc37dadf3aab3542e888e2d Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Fri, 11 Mar 2016 05:40:17 +0000 Subject: [PATCH 007/153] Fix requirements.md link and improve PostgreSQL commands - Point requirements.md link to the correct location - Link to gitlab installation.md document instead of github - Use psql command switch to run specified SQL commands for creation of user and database permissions. --- doc/install/installation.md | 15 ++++----------- doc/install/requirements.md | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 0fd54be58b..9037a332a3 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -22,7 +22,7 @@ If the highest number stable branch is unclear please check the [GitLab Blog](ht This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880). -This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/). +This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [requirements.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/). This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). @@ -160,19 +160,12 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Install the database packages sudo apt-get install -y postgresql postgresql-client libpq-dev - - # Login to PostgreSQL - sudo -u postgres psql -d template1 - + # Create a user for GitLab - # Do not type the 'template1=#', this is part of the prompt - template1=# CREATE USER git CREATEDB; + sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" # Create the GitLab production database & grant all privileges on database - template1=# CREATE DATABASE gitlabhq_production OWNER git; - - # Quit the database session - template1=# \q + sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;" # Try connecting to the new database with the new user sudo -u git -H psql -d gitlabhq_production diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 8df142c531..a4f78613e3 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -22,7 +22,7 @@ For the installations options please see [the installation page on the GitLab we - FreeBSD On the above unsupported distributions is still possible to install GitLab yourself. -Please see the [installation from source guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. +Please see the [installation from source guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. ### Non-Unix operating systems such as Windows From fb5c2147a9f2b3acc6ad5297c737da0f5546c247 Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Sat, 27 Feb 2016 09:28:00 +0100 Subject: [PATCH 008/153] Do not serve anything via nginx as we have workhorse Otherwise this might 'hide' problems https://github.com/gitlabhq/gitlabhq/issues/10053#issuecomment-188919319 --- lib/support/nginx/gitlab | 11 ++++++++++- lib/support/nginx/gitlab-ssl | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index fc5475c4ee..1324e4cd26 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -30,7 +30,6 @@ server { listen [::]:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice - root /home/git/gitlab/public; ## See app/controllers/application_controller.rb for headers set @@ -57,4 +56,14 @@ server { proxy_pass http://gitlab-workhorse; } + + error_page 404 /404.html; + error_page 422 /422.html; + error_page 500 /500.html; + error_page 502 /502.html; + location ~ ^/(404|422|500|502)\.html$ { + root /home/git/gitlab/public; + internal; + } + } diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 1e5f85413e..af6ea9ed70 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -45,7 +45,6 @@ server { listen [::]:443 ipv6only=on ssl default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice - root /home/git/gitlab/public; ## Strong SSL Security ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ @@ -101,4 +100,13 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://gitlab-workhorse; } + + error_page 404 /404.html; + error_page 422 /422.html; + error_page 500 /500.html; + error_page 502 /502.html; + location ~ ^/(404|422|500|502)\.html$ { + root /home/git/gitlab/public; + internal; + } } From 8fa2469bebf814a821da7883addc7c6c37e4a704 Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Sat, 27 Feb 2016 11:59:29 +0100 Subject: [PATCH 009/153] Lets have CSS and logo inline This would work independenty from location and configuration https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/662#note_3973448 --- CHANGELOG | 1 + public/404.html | 44 ++++++++++++++++++++++++++++++++++++++++++-- public/422.html | 45 ++++++++++++++++++++++++++++++++++++++++++--- public/500.html | 44 ++++++++++++++++++++++++++++++++++++++++++-- public/502.html | 44 ++++++++++++++++++++++++++++++++++++++++++-- public/deploy.html | 41 +++++++++++++++++++++++++++++++++++++++-- public/logo.svg | 9 --------- public/static.css | 36 ------------------------------------ 8 files changed, 208 insertions(+), 56 deletions(-) delete mode 100644 public/logo.svg delete mode 100644 public/static.css diff --git a/CHANGELOG b/CHANGELOG index c1c90903d5..2c67ee244f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ v 8.5.5 - Prevent a 500 error in Todos when author was removed - Fix pagination for filtered dashboard and explore pages - Fix "Show all" link behavior + - HTTP error pages work independently from location and config (Artem Sidorenko) v 8.5.4 - Do not cache requests for badges (including builds badge) diff --git a/public/404.html b/public/404.html index a0106bc760..4862770cc2 100644 --- a/public/404.html +++ b/public/404.html @@ -2,11 +2,51 @@ The page you're looking for could not be found (404) - + -

404

+

+
+ 404 +

The page you're looking for could not be found.


Make sure the address is correct and that the page hasn't moved.

diff --git a/public/422.html b/public/422.html index 026997b48e..055b0bde16 100644 --- a/public/422.html +++ b/public/422.html @@ -2,12 +2,51 @@ The change you requested was rejected (422) - + - -

422

+

+
+ 422 +

The change you requested was rejected.


Make sure you have access to the thing you tried to change.

diff --git a/public/500.html b/public/500.html index 08c11bbd05..3d59d1392f 100644 --- a/public/500.html +++ b/public/500.html @@ -2,10 +2,50 @@ Something went wrong (500) - + -

500

+

+
+ 500 +

Whoops, something went wrong on our end.


Try refreshing the page, or going back and attempting the action again.

diff --git a/public/502.html b/public/502.html index 9480a92843..67dfd8a274 100644 --- a/public/502.html +++ b/public/502.html @@ -2,10 +2,50 @@ GitLab is not responding (502) - + -

502

+

+
+ 502 +

Whoops, GitLab is taking too much time to respond.


Try refreshing the page, or going back and attempting the action again.

diff --git a/public/deploy.html b/public/deploy.html index 3822ed4b64..48976dacf4 100644 --- a/public/deploy.html +++ b/public/deploy.html @@ -2,12 +2,49 @@ Deploy in progress - +

-
+
Deploy in progress

Please try again in a few minutes.

diff --git a/public/logo.svg b/public/logo.svg deleted file mode 100644 index fc4553137f..0000000000 --- a/public/logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/public/static.css b/public/static.css deleted file mode 100644 index 0a2b6060d4..0000000000 --- a/public/static.css +++ /dev/null @@ -1,36 +0,0 @@ -body { - color: #666; - text-align: center; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - margin: 0; - width: 800px; - margin: auto; - font-size: 14px; -} - -h1 { - font-size: 56px; - line-height: 100px; - font-weight: normal; - color: #456; -} - -h2 { - font-size: 24px; - color: #666; - line-height: 1.5em; -} - -h3 { - color: #456; - font-size: 20px; - font-weight: normal; - line-height: 28px; -} - -hr { - margin: 18px 0; - border: 0; - border-top: 1px solid #EEE; - border-bottom: 1px solid white; -} From 42fcd3881fcece5c9bd4b720460d6cade573b151 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Thu, 10 Mar 2016 22:08:11 +0100 Subject: [PATCH 010/153] External Users The user has the rights of a public user execpt it can never create a project, group, or team. Also it cant view internal projects. --- app/controllers/admin/users_controller.rb | 2 +- app/finders/projects_finder.rb | 11 +++-- app/models/ability.rb | 34 +++++++-------- app/models/user.rb | 12 ++++++ app/views/admin/users/_form.html.haml | 5 +++ app/views/admin/users/show.html.haml | 4 ++ ...160310185910_add_external_flag_to_users.rb | 5 +++ db/schema.rb | 3 +- lib/api/users.rb | 5 ++- .../security/project/internal_access_spec.rb | 37 ++++++++++++----- .../security/project/private_access_spec.rb | 34 ++++++++++----- .../security/project/public_access_spec.rb | 41 ++++++++++++++----- spec/models/user_spec.rb | 16 ++++++++ spec/requests/api/users_spec.rb | 20 +++++++++ spec/support/matchers/access_matchers.rb | 2 + 15 files changed, 171 insertions(+), 60 deletions(-) create mode 100644 db/migrate/20160310185910_add_external_flag_to_users.rb diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 87f4fb455b..be192964a9 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -150,7 +150,7 @@ class Admin::UsersController < Admin::ApplicationController :email, :remember_me, :bio, :name, :username, :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, :hide_no_password, - :projects_limit, :can_create_group, :admin, :key_id + :projects_limit, :can_create_group, :admin, :key_id, :external ) end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 0e5a8f5ee0..711d9019a3 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -51,13 +51,12 @@ class ProjectsFinder end def all_projects(current_user) - if current_user - [ - current_user.authorized_projects, - public_and_internal_projects - ] + return [Project.public_only] unless current_user + + if current_user.external? + [current_user.authorized_projects, public_projects] else - [Project.public_only] + [current_user.authorized_projects, public_and_internal_projects] end end diff --git a/app/models/ability.rb b/app/models/ability.rb index fe9e0aab71..ccac08b7d3 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -109,23 +109,10 @@ class Ability key = "/user/#{user.id}/project/#{project.id}" RequestStore.store[key] ||= begin - team = project.team + # Push abilities on the users team role + rules.push(*project_team_rules(project.team, user)) - # Rules based on role in project - if team.master?(user) - rules.push(*project_master_rules) - - elsif team.developer?(user) - rules.push(*project_dev_rules) - - elsif team.reporter?(user) - rules.push(*project_report_rules) - - elsif team.guest?(user) - rules.push(*project_guest_rules) - end - - if project.public? || project.internal? + if project.public? || (project.internal? && !user.external?) rules.push(*public_project_rules) # Allow to read builds for internal projects @@ -148,6 +135,19 @@ class Ability end end + def project_team_rules(team, user) + # Rules based on role in project + if team.master?(user) + project_master_rules + elsif team.developer?(user) + project_dev_rules + elsif team.reporter?(user) + project_report_rules + elsif team.guest?(user) + project_guest_rules + end + end + def public_project_rules @public_project_rules ||= project_guest_rules + [ :download_code, @@ -356,7 +356,7 @@ class Ability ] end - if snippet.public? || snippet.internal? + if snippet.public? || (snippet.internal? && !user.external?) rules << :read_personal_snippet end diff --git a/app/models/user.rb b/app/models/user.rb index 043bc825ad..adff238c60 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -59,6 +59,7 @@ # hide_project_limit :boolean default(FALSE) # unlock_token :string # otp_grace_period_started_at :datetime +# external :boolean default(FALSE) # require 'carrierwave/orm/activerecord' @@ -77,6 +78,7 @@ class User < ActiveRecord::Base add_authentication_token_field :authentication_token default_value_for :admin, false + default_value_for :external, false default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_team, false default_value_for :hide_no_ssh_key, false @@ -179,6 +181,7 @@ class User < ActiveRecord::Base after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? } before_save :ensure_authentication_token + before_save :ensure_external_user_rights after_save :ensure_namespace_correct after_initialize :set_projects_limit after_create :post_create_hook @@ -848,4 +851,13 @@ class User < ActiveRecord::Base def send_devise_notification(notification, *args) devise_mailer.send(notification, self, *args).deliver_later end + + def ensure_external_user_rights + return unless self.external? + + self.can_create_team = false + self.can_create_group = false + self.projects_limit = 0 + self.hide_project_limit = true + end end diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index e18dd9bc90..5506827edc 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -61,6 +61,11 @@ .col-sm-10 You cannot remove your own admin rights - else .col-sm-10= f.check_box :admin + + .form-group + = f.label :external, class: 'control-label' + .col-sm-10= f.check_box :external + %fieldset %legend Profile .form-group diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 2bdbae1958..d37489bebe 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -47,6 +47,10 @@ - else Disabled + %li + %span.light External User: + %strong + = @user.external? ? "Yes" : "No" %li %span.light Can create groups: %strong diff --git a/db/migrate/20160310185910_add_external_flag_to_users.rb b/db/migrate/20160310185910_add_external_flag_to_users.rb new file mode 100644 index 0000000000..54937f1eb7 --- /dev/null +++ b/db/migrate/20160310185910_add_external_flag_to_users.rb @@ -0,0 +1,5 @@ +class AddExternalFlagToUsers < ActiveRecord::Migration + def change + add_column :users, :external, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 3ac6203632..16e28d7a3f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160309140734) do +ActiveRecord::Schema.define(version: 20160310185910) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -929,6 +929,7 @@ ActiveRecord::Schema.define(version: 20160309140734) do t.string "unlock_token" t.datetime "otp_grace_period_started_at" t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/lib/api/users.rb b/lib/api/users.rb index fd2128bd17..c574f042a6 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -61,19 +61,20 @@ module API # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false # confirm - Require user confirmation - true (default) or false + # external - Is user an external user - true or false(default) # Example Request: # POST /users post do authenticated_as_admin! required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm, :external] admin = attrs.delete(:admin) confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i)) user = User.build_user(attrs) user.admin = admin unless admin.nil? user.skip_confirmation! unless confirm - identity_attrs = attributes_for_keys [:provider, :extern_uid] + if identity_attrs.any? user.identities.build(identity_attrs) end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 57563add74..31bd80e7cc 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -34,6 +34,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -45,6 +46,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -56,6 +58,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -67,6 +70,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -78,6 +82,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -89,22 +94,21 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/blob" do - before do - commit = project.repository.commit - path = '.gitignore' - @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path)) - end + let(:commit) { project.repository.commit } + subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) } - it { expect(@blob_path).to be_allowed_for master } - it { expect(@blob_path).to be_allowed_for reporter } - it { expect(@blob_path).to be_allowed_for :admin } - it { expect(@blob_path).to be_allowed_for guest } - it { expect(@blob_path).to be_allowed_for :user } - it { expect(@blob_path).to be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/edit" do @@ -115,6 +119,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -126,6 +131,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -137,6 +143,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -149,6 +156,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -160,6 +168,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -171,6 +180,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -182,6 +192,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -193,6 +204,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -209,6 +221,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -225,6 +238,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -236,6 +250,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index a1e111c6ca..13ff29c98a 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -34,6 +34,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -45,6 +46,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -56,6 +58,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -78,6 +81,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -89,22 +93,21 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/blob" do - before do - commit = project.repository.commit - path = '.gitignore' - @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path)) - end + let(:commit) { project.repository.commit } + subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))} - it { expect(@blob_path).to be_allowed_for master } - it { expect(@blob_path).to be_allowed_for reporter } - it { expect(@blob_path).to be_allowed_for :admin } - it { expect(@blob_path).to be_denied_for guest } - it { expect(@blob_path).to be_denied_for :user } - it { expect(@blob_path).to be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/edit" do @@ -115,6 +118,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -126,6 +130,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -137,6 +142,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -149,6 +155,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -160,6 +167,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -171,6 +179,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -187,6 +196,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -203,6 +213,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -214,6 +225,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index b98476f854..4e13507636 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -38,6 +38,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -49,6 +50,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -60,6 +62,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -71,6 +74,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -82,6 +86,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -93,6 +98,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -107,6 +113,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -118,6 +125,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end end @@ -135,6 +143,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -146,23 +155,22 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end end describe "GET /:project_path/blob" do - before do - commit = project.repository.commit - path = '.gitignore' - @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path)) - end + let(:commit) { project.repository.commit } - it { expect(@blob_path).to be_allowed_for master } - it { expect(@blob_path).to be_allowed_for reporter } - it { expect(@blob_path).to be_allowed_for :admin } - it { expect(@blob_path).to be_allowed_for guest } - it { expect(@blob_path).to be_allowed_for :user } - it { expect(@blob_path).to be_allowed_for :visitor } + subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) } + + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/edit" do @@ -173,6 +181,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -184,6 +193,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -195,6 +205,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -207,6 +218,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -218,6 +230,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -229,6 +242,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -240,6 +254,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -251,6 +266,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end @@ -267,6 +283,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -283,6 +300,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } it { is_expected.to be_allowed_for :visitor } end @@ -294,6 +312,7 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 909b679659..8642654ad3 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -206,6 +206,21 @@ describe User, models: true do it { is_expected.to respond_to(:is_admin?) } it { is_expected.to respond_to(:name) } it { is_expected.to respond_to(:private_token) } + it { is_expected.to respond_to(:external?) } + end + + describe 'before save hook' do + context 'when saving an external user' do + let(:user) { create(:user) } + let(:external_user) { create(:user, external: true) } + + it "sets other properties aswell" do + expect(external_user.can_create_team).to be_falsey + expect(external_user.can_create_group).to be_falsey + expect(external_user.hide_project_limit).to be_truthy + expect(external_user.projects_limit).to be 0 + end + end end describe '#confirm' do @@ -430,6 +445,7 @@ describe User, models: true do expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit) expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group) expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme) + expect(user.external).to be_falsey end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 96e8c8c51f..5366a7bd06 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -120,6 +120,26 @@ describe API::API, api: true do expect(response.status).to eq(201) end + it 'creates non-external users by default' do + post api("/users", admin), attributes_for(:user) + expect(response.status).to eq(201) + + user_id = json_response['id'] + new_user = User.find(user_id) + expect(new_user).not_to eq nil + expect(new_user.external).to be_falsy + end + + it 'should allow an external user to be created' do + post api("/users", admin), attributes_for(:user, external: true) + expect(response.status).to eq(201) + + user_id = json_response['id'] + new_user = User.find(user_id) + expect(new_user).not_to eq nil + expect(new_user.external).to be_truthy + end + it "should not create user with invalid email" do post api('/users', admin), email: 'invalid email', diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb index 558e8b1612..4e007c777e 100644 --- a/spec/support/matchers/access_matchers.rb +++ b/spec/support/matchers/access_matchers.rb @@ -15,6 +15,8 @@ module AccessMatchers logout when :admin login_as(create(:admin)) + when :external + login_as(create(:user, external: true)) when User login_as(user) else From fc8d64b3a0fc5aeb048b2bce79b2ae93a4fcc798 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Sat, 12 Mar 2016 15:02:38 +0100 Subject: [PATCH 011/153] Remove Project#publicish --- app/models/project.rb | 6 ------ .../dashboard/projects/_zero_authorized_projects.html.haml | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 1f18ad7816..51952ba72a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -252,12 +252,6 @@ class Project < ActiveRecord::Base where('projects.last_activity_at < ?', 6.months.ago) end - def publicish(user) - visibility_levels = [Project::PUBLIC] - visibility_levels << Project::INTERNAL if user - where(visibility_level: visibility_levels) - end - def with_push joins(:events).where('events.action = ?', Event::PUSHED) end diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index c3efa7727b..65edc05b79 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,4 +1,4 @@ -- publicish_project_count = Project.publicish(current_user).count +- publicish_project_count = ProjectsFinder.new.execute(current_user).count %h3.page-title Welcome to GitLab! %p.light Self hosted Git management application. %hr @@ -18,7 +18,7 @@ - if current_user.can_create_project? .link_holder = link_to new_project_path, class: "btn btn-new" do - %i.fa.fa-plus + =icon('plus') New Project - if current_user.can_create_group? From 76eeb316df2f256d0d3c41d97421f709a21a02a8 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Sun, 13 Mar 2016 10:05:32 +0100 Subject: [PATCH 012/153] Create an external users tab on Admin user list Also incorporates the review into this, mainly spec changes. --- CHANGELOG | 1 + app/finders/projects_finder.rb | 13 ++++++++---- app/models/user.rb | 5 +++-- app/views/admin/users/_form.html.haml | 3 ++- app/views/admin/users/index.html.haml | 4 ++++ .../security/project/internal_access_spec.rb | 20 +++++++++++++++++++ .../security/project/private_access_spec.rb | 18 +++++++++++++++++ spec/models/user_spec.rb | 1 - 8 files changed, 57 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 07b187b31a..f9c6f6e23e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.6.0 (unreleased) - Add main language of a project in the list of projects (Tiago Botelho) - Add ability to show archived projects on dashboard, explore and group pages - Move group activity to separate page + - Create external users which are excluded of internal and private projects unless access was explicitly granted v 8.5.5 - Ensure removing a project removes associated Todo entries diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 711d9019a3..4a6c2fbd71 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -40,18 +40,23 @@ class ProjectsFinder private def group_projects(current_user, group) - if current_user + return [group.projects.public_only] unless current_user + + if current_user.external? + [ + group_projects_for_user(current_user, group), + group.projects.public_only + ] + else [ group_projects_for_user(current_user, group), group.projects.public_and_internal_only ] - else - [group.projects.public_only] end end def all_projects(current_user) - return [Project.public_only] unless current_user + return [public_projects] unless current_user if current_user.external? [current_user.authorized_projects, public_projects] diff --git a/app/models/user.rb b/app/models/user.rb index adff238c60..34f1cfc0be 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -229,6 +229,7 @@ class User < ActiveRecord::Base # Scopes scope :admins, -> { where(admin: true) } scope :blocked, -> { with_states(:blocked, :ldap_blocked) } + scope :external, -> { where(external: true) } scope :active, -> { with_state(:active) } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } @@ -284,6 +285,8 @@ class User < ActiveRecord::Base self.with_two_factor when 'wop' self.without_projects + when 'external' + self.external else self.active end @@ -855,9 +858,7 @@ class User < ActiveRecord::Base def ensure_external_user_rights return unless self.external? - self.can_create_team = false self.can_create_group = false self.projects_limit = 0 - self.hide_project_limit = true end end diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 5506827edc..b910040a16 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -58,13 +58,14 @@ = f.label :admin, class: 'control-label' - if current_user == @user .col-sm-10= f.check_box :admin, disabled: true - .col-sm-10 You cannot remove your own admin rights + .col-sm-10 You cannot remove your own admin rights. - else .col-sm-10= f.check_box :admin .form-group = f.label :external, class: 'control-label' .col-sm-10= f.check_box :external + .col-sm-10 External users can not see internal or private projects unless access is explicitly granted. Also, external user can not create projects or groups. %fieldset %legend Profile diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index b6b1168bd3..4394e18100 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -19,6 +19,10 @@ = link_to admin_users_path(filter: 'two_factor_disabled') do 2FA Disabled %small.badge= number_with_delimiter(User.without_two_factor.count) + %li.filter-external{class: "#{'active' if params[:filter] == 'external'}"} + = link_to admin_users_path(filter: 'external') do + External + %small.badge= number_with_delimiter(User.external.count) %li{class: "#{'active' if params[:filter] == "blocked"}"} = link_to admin_users_path(filter: "blocked") do Blocked diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 31bd80e7cc..f88c591d89 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -8,10 +8,12 @@ describe "Internal Project Access", feature: true do let(:master) { create(:user) } let(:guest) { create(:user) } let(:reporter) { create(:user) } + let(:external_team_member) { create(:user, external: true) } before do # full access project.team << [master, :master] + project.team << [external_team_member, :master] # readonly project.team << [reporter, :reporter] @@ -35,6 +37,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -47,6 +50,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -59,6 +63,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -71,6 +76,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -83,6 +89,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -95,6 +102,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -108,6 +116,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -120,6 +129,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -132,6 +142,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -144,6 +155,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -157,6 +169,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -169,6 +182,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -181,6 +195,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -193,6 +208,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -205,6 +221,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -222,6 +239,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -239,6 +257,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for guest } it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -251,6 +270,7 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 13ff29c98a..19f287ce7a 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -8,10 +8,12 @@ describe "Private Project Access", feature: true do let(:master) { create(:user) } let(:guest) { create(:user) } let(:reporter) { create(:user) } + let(:external_team_member) { create(:user, external: true) } before do # full access project.team << [master, :master] + project.team << [external_team_member, :master] # readonly project.team << [reporter, :reporter] @@ -35,6 +37,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -47,6 +50,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -59,6 +63,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -70,6 +75,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -82,6 +88,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -94,6 +101,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -107,6 +115,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -119,6 +128,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -131,6 +141,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -143,6 +154,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -156,6 +168,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -168,6 +181,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -180,6 +194,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -197,6 +212,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -214,6 +230,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end @@ -226,6 +243,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for external_team_member } it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8642654ad3..ca0c84c863 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -217,7 +217,6 @@ describe User, models: true do it "sets other properties aswell" do expect(external_user.can_create_team).to be_falsey expect(external_user.can_create_group).to be_falsey - expect(external_user.hide_project_limit).to be_truthy expect(external_user.projects_limit).to be 0 end end From a6f5304280d95c496b7669d1553cc6c246f11f94 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 14 Mar 2016 19:28:23 +0200 Subject: [PATCH 013/153] Merge duplicate cache entry in CI docs [ci skip] --- doc/ci/yaml/README.md | 108 +++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 60 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 051eaa0415..4dd5fe1f76 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -134,6 +134,9 @@ thus allowing to fine tune them. ### cache +>**Note:** +Introduced in GitLab Runner v0.7.0. + `cache` is used to specify a list of files and directories which should be cached between builds. @@ -142,18 +145,59 @@ cached between builds. If `cache` is defined outside the scope of the jobs, it means it is set globally and all jobs will use its definition. -To cache all git untracked files and files in `binaries`: +Cache all files in `binaries` and `.config`: + +```yaml +rspec: + script: test + cache: + paths: + - binaries/ + - .config +``` + +Cache all Git untracked files: + +```yaml +rspec: + script: test + cache: + untracked: true +``` + +Cache all Git untracked files and files in `binaries`: + +```yaml +rspec: + script: test + cache: + untracked: true + paths: + - binaries/ +``` + +Locally defined cache overwrites globally defined options. This will cache only +`binaries/`: ```yaml cache: - untracked: true paths: - - binaries/ + - my/files + +rspec: + script: test + cache: + paths: + - binaries/ ``` +The cache is provided on best effort basis, so don't expect that cache will be +always present. For implementation details please check GitLab Runner. + #### cache:key -_**Note:** Introduced in GitLab Runner v1.0.0._ +>**Note:** +Introduced in GitLab Runner v1.0.0. The `key` directive allows you to define the affinity of caching between jobs, allowing to have a single cache for all jobs, @@ -453,62 +497,6 @@ release-job: The artifacts will be sent to GitLab after a successful build and will be available for download in the GitLab UI. -### cache - -_**Note:** Introduced in GitLab Runner v0.7.0._ - -`cache` is used to specify list of files and directories which should be cached -between builds. Below are some examples: - -Cache all files in `binaries` and `.config`: - -```yaml -rspec: - script: test - cache: - paths: - - binaries/ - - .config -``` - -Cache all git untracked files: - -```yaml -rspec: - script: test - cache: - untracked: true -``` - -Cache all git untracked files and files in `binaries`: - -```yaml -rspec: - script: test - cache: - untracked: true - paths: - - binaries/ -``` - -Locally defined cache overwrites globally defined options. This will cache only -`binaries/`: - -```yaml -cache: - paths: - - my/files - -rspec: - script: test - cache: - paths: - - binaries/ -``` - -The cache is provided on best effort basis, so don't expect that cache will be -always present. For implementation details please check GitLab Runner. - ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. From ab418e27a9121704d623343417126b0bdae08e79 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 14 Mar 2016 22:06:23 +0100 Subject: [PATCH 014/153] Improve external users feature --- app/finders/projects_finder.rb | 4 ++-- app/views/admin/users/_form.html.haml | 2 +- app/views/admin/users/index.html.haml | 6 ++++-- .../dashboard/projects/_zero_authorized_projects.html.haml | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 70c073f7d5..3a5fc5b590 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -47,9 +47,9 @@ class ProjectsFinder group.shared_projects.visible_to_user(current_user) ] if current_user.external? - user_group_projects.push(group.projects.public_only) + user_group_projects << group.projects.public_only else - user_group_projects.push(group.projects.public_and_internal_only) + user_group_projects << group.projects.public_and_internal_only end end diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index b910040a16..d2527ede99 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -65,7 +65,7 @@ .form-group = f.label :external, class: 'control-label' .col-sm-10= f.check_box :external - .col-sm-10 External users can not see internal or private projects unless access is explicitly granted. Also, external user can not create projects or groups. + .col-sm-10 External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups. %fieldset %legend Profile diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 4394e18100..0ee8dc962b 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -74,12 +74,14 @@ %li .list-item-name - if user.blocked? - %i.fa.fa-lock.cred + = icon("lock", class: "cred") - else - %i.fa.fa-user.cgreen + = icon("user", class: "cgreen") = link_to user.name, [:admin, user] - if user.admin? %strong.cred (Admin) + - if user.external? + %strong.cred (External) - if user == current_user %span.cred It's you! .pull-right diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index 65edc05b79..d54c7cad7b 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -18,7 +18,7 @@ - if current_user.can_create_project? .link_holder = link_to new_project_path, class: "btn btn-new" do - =icon('plus') + = icon('plus') New Project - if current_user.can_create_group? From ae090b1c8a572b0dca83e81f99adce01a50713bf Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 15 Mar 2016 04:12:24 +0000 Subject: [PATCH 015/153] Revert to relative requirements.md link use gitlab.com link for installation guides. --- doc/install/installation.md | 2 +- doc/install/requirements.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 9037a332a3..303516840b 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -22,7 +22,7 @@ If the highest number stable branch is unclear please check the [GitLab Blog](ht This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880). -This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [requirements.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/). +This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [requirements.md](requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/). This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). diff --git a/doc/install/requirements.md b/doc/install/requirements.md index a4f78613e3..a7dfa7ddf5 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -22,7 +22,7 @@ For the installations options please see [the installation page on the GitLab we - FreeBSD On the above unsupported distributions is still possible to install GitLab yourself. -Please see the [installation from source guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. +Please see the [installation from source guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) and the [installation guides](https://about.gitlab.com/installation/) for more information. ### Non-Unix operating systems such as Windows From b5c8f5727b5261d86ce52b976a355e7f223a563a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 15 Mar 2016 11:34:49 +0100 Subject: [PATCH 016/153] Hide builds from Project's settings when the feature is disabled --- CHANGELOG | 1 + app/assets/javascripts/project_new.js.coffee | 13 ++++ app/views/projects/_builds_settings.html.haml | 60 +++++++++++++++++ app/views/projects/edit.html.haml | 65 +------------------ 4 files changed, 76 insertions(+), 63 deletions(-) create mode 100644 app/views/projects/_builds_settings.html.haml diff --git a/CHANGELOG b/CHANGELOG index d38646ece6..473acdaa5e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.6.0 (unreleased) - Rewrite logo to simplify SVG code (Sean Lang) - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) - Ignore jobs that start with `.` (hidden jobs) + - Hide builds from project's settings when the feature is disabled - Allow to pass name of created artifacts archive in `.gitlab-ci.yml` - Refactor and greatly improve search performance - Add support for cross-project label references diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee index fecdb9fc2e..63dee4ed5d 100644 --- a/app/assets/javascripts/project_new.js.coffee +++ b/app/assets/javascripts/project_new.js.coffee @@ -3,3 +3,16 @@ class @ProjectNew $('.project-edit-container').on 'ajax:before', => $('.project-edit-container').hide() $('.save-project-loader').show() + @toggleSettings() + @toggleSettingsOnclick() + + + toggleSettings: -> + checked = $("#project_builds_enabled").prop("checked") + if checked + $('.builds-feature').show() + else + $('.builds-feature').hide() + + toggleSettingsOnclick: -> + $("#project_builds_enabled").on 'click', @toggleSettings diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml new file mode 100644 index 0000000000..95ab9ecf3e --- /dev/null +++ b/app/views/projects/_builds_settings.html.haml @@ -0,0 +1,60 @@ +%fieldset.builds-feature + %legend + Builds: + .form-group + .col-sm-offset-2.col-sm-10 + %p Get recent application code using the following command: + .radio + = f.label :build_allow_git_fetch_false do + = f.radio_button :build_allow_git_fetch, 'false' + %strong git clone + %br + %span.descr Slower but makes sure you have a clean dir before every build + .radio + = f.label :build_allow_git_fetch_true do + = f.radio_button :build_allow_git_fetch, 'true' + %strong git fetch + %br + %span.descr Faster + + .form-group + = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label' + .col-sm-10 + = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0' + %p.help-block per build in minutes + .form-group + = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label' + .col-sm-10 + .input-group + %span.input-group-addon / + = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered' + %span.input-group-addon / + %p.help-block + We will use this regular expression to find test coverage output in build trace. + Leave blank if you want to disable this feature + .bs-callout.bs-callout-info + %p Below are examples of regex for existing tools: + %ul + %li + Simplecov (Ruby) - + %code \(\d+.\d+\%\) covered + %li + pytest-cov (Python) - + %code \d+\%\s*$ + %li + phpunit --coverage-text --colors=never (PHP) - + %code ^\s*Lines:\s*\d+.\d+\% + + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :public_builds do + = f.check_box :public_builds + %strong Public builds + .help-block Allow everyone to access builds for Public and Internal projects + + .form-group + = f.label :runners_token, "Runners token", class: 'control-label' + .col-sm-10 + = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89' + %p.help-block The secure token used to checkout project. diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f2e56081af..6d872cd0b2 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -84,6 +84,8 @@ %br %span.descr Share code pastes with others out of git repository + = render 'builds_settings', f: f + %fieldset.features %legend Project avatar: @@ -110,69 +112,6 @@ %hr = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - %fieldset.features - %legend - Continuous Integration - .form-group - .col-sm-offset-2.col-sm-10 - %p Get recent application code using the following command: - .radio - = f.label :build_allow_git_fetch_false do - = f.radio_button :build_allow_git_fetch, 'false' - %strong git clone - %br - %span.descr Slower but makes sure you have a clean dir before every build - .radio - = f.label :build_allow_git_fetch_true do - = f.radio_button :build_allow_git_fetch, 'true' - %strong git fetch - %br - %span.descr Faster - - .form-group - = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label' - .col-sm-10 - = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0' - %p.help-block per build in minutes - .form-group - = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label' - .col-sm-10 - .input-group - %span.input-group-addon / - = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered' - %span.input-group-addon / - %p.help-block - We will use this regular expression to find test coverage output in build trace. - Leave blank if you want to disable this feature - .bs-callout.bs-callout-info - %p Below are examples of regex for existing tools: - %ul - %li - Simplecov (Ruby) - - %code \(\d+.\d+\%\) covered - %li - pytest-cov (Python) - - %code \d+\%\s*$ - %li - phpunit --coverage-text --colors=never (PHP) - - %code ^\s*Lines:\s*\d+.\d+\% - - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.label :public_builds do - = f.check_box :public_builds - %strong Public builds - .help-block Allow everyone to access builds for Public and Internal projects - - %fieldset.features - %legend - Advanced settings - .form-group - = f.label :runners_token, "CI token", class: 'control-label' - .col-sm-10 - = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89' - %p.help-block The secure token used to checkout project. .form-actions = f.submit 'Save changes', class: "btn btn-save" From 8acad49fe0a492c2f4e16756135679b3a190543b Mon Sep 17 00:00:00 2001 From: Hiroyuki Sato Date: Sun, 28 Feb 2016 21:11:43 +0900 Subject: [PATCH 017/153] Support Wiki with UTF-8 page name --- CHANGELOG | 1 + Gemfile | 4 +++- Gemfile.lock | 6 +++++- app/models/project_wiki.rb | 8 ++++---- app/models/wiki_page.rb | 2 +- config/initializers/gollum.rb | 13 +++++++++++++ 6 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 config/initializers/gollum.rb diff --git a/CHANGELOG b/CHANGELOG index 015efa05c6..d19c694863 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.6.0 (unreleased) setup. A password can be provided during setup (see installation docs), or GitLab will ask the user to create a new one upon first visit. - Fix issue when pushing to projects ending in .wiki + - Add support for wiki with UTF-8 page names (Hiroyuki Sato) - Don't load all of GitLab in mail_room - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - Memoize @group in Admin::GroupsController (Yatish Mehta) diff --git a/Gemfile b/Gemfile index a0e8e79662..a849d7493a 100644 --- a/Gemfile +++ b/Gemfile @@ -58,7 +58,9 @@ gem "gitlab_git", '~> 9.0' gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" # Git Wiki -gem 'gollum-lib', '~> 4.1.0' +# Required manually in config/initializers/gollum.rb to control load order +gem 'gollum-lib', '~> 4.1.0', require: false +gem 'gollum-rugged_adapter', '~> 0.4.2', require: false # Language detection gem "github-linguist", "~> 4.7.0", require: "linguist" diff --git a/Gemfile.lock b/Gemfile.lock index f4f5649eb7..669bfcf4d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -381,6 +381,9 @@ GEM rouge (~> 1.9) sanitize (~> 2.1.0) stringex (~> 2.5.1) + gollum-rugged_adapter (0.4.2) + mime-types (>= 1.15) + rugged (~> 0.24.0, >= 0.21.3) gon (6.0.1) actionpack (>= 3.0) json @@ -703,7 +706,7 @@ GEM rubyntlm (0.5.2) rubypants (0.2.0) rufus-scheduler (3.1.10) - rugged (0.24.0b13) + rugged (0.24.0) safe_yaml (1.0.4) sanitize (2.1.0) nokogiri (>= 1.4.4) @@ -941,6 +944,7 @@ DEPENDENCIES gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.1.0) + gollum-rugged_adapter (~> 0.4.2) gon (~> 6.0.1) grape (~> 0.13.0) grape-entity (~> 0.4.2) diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index c96e6f0b8e..59b1b86d1f 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -2,7 +2,7 @@ class ProjectWiki include Gitlab::ShellAdapter MARKUPS = { - 'Markdown' => :md, + 'Markdown' => :markdown, 'RDoc' => :rdoc, 'AsciiDoc' => :asciidoc } unless defined?(MARKUPS) @@ -47,7 +47,7 @@ class ProjectWiki def wiki @wiki ||= begin Gollum::Wiki.new(path_to_repo) - rescue Gollum::NoSuchPathError + rescue Rugged::OSError create_repo! end end @@ -90,7 +90,7 @@ class ProjectWiki def create_page(title, content, format = :markdown, message = nil) commit = commit_details(:created, message, title) - wiki.write_page(title, format, content, commit) + wiki.write_page(title, format.to_sym, content, commit) update_project_activity rescue Gollum::DuplicatePageError => e @@ -101,7 +101,7 @@ class ProjectWiki def update_page(page, content, format = :markdown, message = nil) commit = commit_details(:updated, message, page.title) - wiki.update_page(page, page.name, format, content, commit) + wiki.update_page(page, page.name, format.to_sym, content, commit) update_project_activity end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index dbd70dc5a4..526760779a 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -62,7 +62,7 @@ class WikiPage # The raw content of this page. def content @attributes[:content] ||= if @page - @page.raw_data + @page.text_data end end diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb new file mode 100644 index 0000000000..703f24f93b --- /dev/null +++ b/config/initializers/gollum.rb @@ -0,0 +1,13 @@ +module Gollum + GIT_ADAPTER = "rugged" +end +require "gollum-lib" + +module Gollum + class Committer + # Patch for UTF-8 path + def method_missing(name, *args) + index.send(name, *args) + end + end +end From b24f9d66bc260dbfc04e63062b06bef390a4ba97 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 15 Mar 2016 17:38:45 +0000 Subject: [PATCH 018/153] Fixed overflow on users name in dropdowns Closes #14289 --- app/assets/stylesheets/framework/dropdowns.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 5b647fc617..309da34da0 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -161,9 +161,8 @@ .dropdown-menu-user-full-name { display: block; - margin-bottom: 2px; font-weight: 600; - line-height: 1; + line-height: 16px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; @@ -171,7 +170,7 @@ .dropdown-menu-user-username { display: block; - line-height: 1; + line-height: 16px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; From aaf4434b0e24da916d4392aa9cd001cdb8e0c7dc Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 15 Mar 2016 00:11:20 +0100 Subject: [PATCH 019/153] Doc external users feature --- doc/README.md | 2 +- doc/api/users.md | 3 ++- doc/permissions/permissions.md | 17 +++++++++++++++++ lib/api/entities.rb | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/doc/README.md b/doc/README.md index 0ca30e4e0f..db19c3de8d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -8,7 +8,7 @@ - [Importing to GitLab](workflow/importing/README.md). - [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab -- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. +- [Permissions](permissions/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do. - [Profile Settings](profile/README.md) - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. diff --git a/doc/api/users.md b/doc/api/users.md index 82c57a2fd4..44a29da5ec 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -194,6 +194,7 @@ Parameters: - `admin` (optional) - User is admin - true or false (default) - `can_create_group` (optional) - User can create groups - true or false - `confirm` (optional) - Require confirmation - true (default) or false +- `external` (optional) - Flags the user as external - true or false(default) ## User modification @@ -560,7 +561,7 @@ Parameters: - `uid` (required) - id of specified user -Will return `200 OK` on success, `404 User Not Found` is user cannot be found or +Will return `200 OK` on success, `404 User Not Found` is user cannot be found or `403 Forbidden` when trying to block an already blocked user by LDAP synchronization. ## Unblock user diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index ac0fd3d175..2dfd08755b 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -71,3 +71,20 @@ Any user can remove themselves from a group, unless they are the last Owner of t | Create project in group | | | | ✓ | ✓ | | Manage group members | | | | | ✓ | | Remove group | | | | | ✓ | + +## External Users + +In cases where it is desired that a user has access to some internal or private projects, but others +should remain hidden from this user, there is the option of creating `External Users`. +An administrator can flag a user as external through the API or by checking the checkbox on the admin panel. + +In the case of a new user: navigate to the **Admin** area and click the **New User** button. If you would like to +edit a user, go to the user list on the **Admin** area and click the **Edit** button. + +External users can only access projects to which they are explicitly granted access, thus hiding all internal projects. +Access can be granted by adding the users as member to the project or by including this user in a group. External users will, like usual users, receive +a role in the project or group with all the abilities that are mentioned in the table above. + +External users cannot create groups or projects, and have the same access as logged out users in all other cases. This feature may be +useful when for example a contractor is working on a given project and should only access the given project and public +projects. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7204dca34b..4b3ad1443b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -31,6 +31,7 @@ module API expose :can_create_group?, as: :can_create_group expose :can_create_project?, as: :can_create_project expose :two_factor_enabled + expose :external end class UserLogin < UserFull From 7c61bdcf6f7383ad26ea5b2c59bc8bc72096a8e6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 15 Mar 2016 11:03:17 -0700 Subject: [PATCH 020/153] Fix Capybara 2.6.2 deprecation warnings --- features/support/capybara.rb | 2 +- spec/support/capybara.rb | 2 +- spec/support/wait_for_ajax.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/support/capybara.rb b/features/support/capybara.rb index f33379f76c..fe9e39cf50 100644 --- a/features/support/capybara.rb +++ b/features/support/capybara.rb @@ -9,7 +9,7 @@ Capybara.register_driver :poltergeist do |app| Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768]) end -Capybara.default_wait_time = timeout +Capybara.default_max_wait_time = timeout Capybara.ignore_hidden_elements = false unless ENV['CI'] || ENV['CI_SERVER'] diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 65d59e6813..e1f90e17cc 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -10,7 +10,7 @@ Capybara.register_driver :poltergeist do |app| Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768]) end -Capybara.default_wait_time = timeout +Capybara.default_max_wait_time = timeout Capybara.ignore_hidden_elements = true unless ENV['CI'] || ENV['CI_SERVER'] diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb index 692d219e9f..b90fc11267 100644 --- a/spec/support/wait_for_ajax.rb +++ b/spec/support/wait_for_ajax.rb @@ -1,6 +1,6 @@ module WaitForAjax def wait_for_ajax - Timeout.timeout(Capybara.default_wait_time) do + Timeout.timeout(Capybara.default_max_wait_time) do loop until finished_all_ajax_requests? end end From 228007dfbcd8f97c66c1802f3b69abd7d02c7d26 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 12 Feb 2016 19:42:25 +0100 Subject: [PATCH 021/153] new-branch-button --- CHANGELOG | 2 ++ app/assets/stylesheets/pages/issues.scss | 5 +++++ app/controllers/projects/branches_controller.rb | 16 +++++++++++++--- app/controllers/projects/issues_controller.rb | 1 + app/models/issue.rb | 14 ++++++++++++++ app/services/merge_requests/build_service.rb | 11 +++++++++++ .../projects/issues/_merge_requests.html.haml | 2 +- app/views/projects/issues/_new_branch.html.haml | 5 +++++ .../projects/issues/_related_branches.html.haml | 15 +++++++++++++++ app/views/projects/issues/show.html.haml | 2 ++ spec/models/issue_spec.rb | 12 ++++++++++++ 11 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 app/views/projects/issues/_new_branch.html.haml create mode 100644 app/views/projects/issues/_related_branches.html.haml diff --git a/CHANGELOG b/CHANGELOG index 7f076f70c7..fe65e7b34f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -175,6 +175,8 @@ v 8.5.0 - Add label description (Nuttanart Pornprasitsakul) - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Add Todos + - User project limit is reached notice is hidden if the projects limit is zero + - New branch button appears on issues where applicable (Zeger-Jan van de Weg) v 8.4.5 - No CE-specific changes diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 1b686c58ea..5f1810cbaf 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -49,6 +49,11 @@ form.edit-issue { margin: 0; } +.related-branches-title { + font-size: 16px; + font-weight: 600; +} + .merge-requests-title { font-size: 16px; font-weight: 600; diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 4db3b3bf23..cf68f50b1f 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -9,7 +9,7 @@ class Projects::BranchesController < Projects::ApplicationController @sort = params[:sort] || 'name' @branches = @repository.branches_sorted_by(@sort) @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE) - + @max_commits = @branches.reduce(0) do |memo, branch| diverging_commit_counts = repository.diverging_commit_counts(branch) [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max @@ -23,8 +23,7 @@ class Projects::BranchesController < Projects::ApplicationController def create branch_name = sanitize(strip_tags(params[:branch_name])) branch_name = Addressable::URI.unescape(branch_name) - ref = sanitize(strip_tags(params[:ref])) - ref = Addressable::URI.unescape(ref) + result = CreateBranchService.new(project, current_user). execute(branch_name, ref) @@ -49,4 +48,15 @@ class Projects::BranchesController < Projects::ApplicationController format.js { render status: status[:return_code] } end end + + private + + def ref + if params[:ref] + ref_escaped = sanitize(strip_tags(params[:ref])) + Addressable::URI.unescape(ref_escaped) + else + @project.default_branch + end + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index b0a03ee45c..aa7a178dcf 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -65,6 +65,7 @@ class Projects::IssuesController < Projects::ApplicationController @notes = @issue.notes.nonawards.with_associations.fresh @noteable = @issue @merge_requests = @issue.referenced_merge_requests(current_user) + @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch) respond_with(@issue) end diff --git a/app/models/issue.rb b/app/models/issue.rb index 5f58c0508f..243d9a5db6 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -94,6 +94,10 @@ class Issue < ActiveRecord::Base end.sort_by(&:iid) end + def related_branches + self.project.repository.branch_names.select { |branch| /\A#{iid}-/ =~ branch } + end + # Reset issue events cache # # Since we do cache @event we need to reset cache in special cases: @@ -120,4 +124,14 @@ class Issue < ActiveRecord::Base note.all_references(current_user).merge_requests end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) } end + + def to_branch_name + "#{iid}-#{title.parameterize}"[0,25] + end + + def new_branch_button?(current_user) + !self.closed? && + referenced_merge_requests(current_user).empty? && + related_branches.empty? + end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 954746a39a..8f1b735b8d 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -47,6 +47,17 @@ module MergeRequests merge_request.title = merge_request.source_branch.titleize.humanize end + # When your branch name starts with an iid followed by a dash this pattern will + # be interpreted as the use wants to close that issue on this project + # Pattern example: 112-fix-mep-mep + # Will lead to appending `Closes #112` to the description + if merge_request.source_branch =~ /\A\d+-/ + closes_issue = "Closes ##{Regexp.last_match(0)[0...-1]}" + closes_issue.prepend("\n") if merge_request.description.present? + + merge_request.description << closes_issue + end + merge_request end diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index d9868ad1f0..d6b38b327f 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -1,4 +1,4 @@ --if @merge_requests.any? +- if @merge_requests.any? %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') %ul.unstyled-list diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml new file mode 100644 index 0000000000..4d5fa61a91 --- /dev/null +++ b/app/views/projects/issues/_new_branch.html.haml @@ -0,0 +1,5 @@ +- if current_user && can?(current_user, :push_code, @project) && @issue.new_branch_button?(current_user) + .pull-right + = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name), method: :post, class: 'btn' do + = icon('code-fork') + New Branch diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml new file mode 100644 index 0000000000..5638766198 --- /dev/null +++ b/app/views/projects/issues/_related_branches.html.haml @@ -0,0 +1,15 @@ +- if @related_branches.any? + %h2.related-branches-title + = pluralize(@related_branches.count, 'Related Branch') + %ul.unstyled-list + - @related_branches.each do |branch| + %li + - sha = @project.repository.find_branch(branch).target + - ci_commit = @project.ci_commit(sha) if sha + - if ci_commit + %span.related-branch-ci-status + = render_ci_status(ci_commit) + %span.related-branch-info + %strong + = link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch) do + = branch diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 0242276cd8..1e8308277c 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -70,8 +70,10 @@ .merge-requests = render 'merge_requests' + = render 'related_branches' .content-block.content-block-small + = render 'new_branch' = render 'votes/votes_block', votable: @issue .row diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 7f44ca2f7d..d572a71cf4 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -140,4 +140,16 @@ describe Issue, models: true do it_behaves_like 'a Taskable' do let(:subject) { create :issue } end + + describe "#to_branch_name" do + let(:issue) { build(:issue, title: 'a' * 30) } + + it "is expected not to exceed 25 chars" do + expect(issue.to_branch_name.length).to eq 25 + end + + it "starts with the issue iid" do + expect(issue.to_branch_name).to match /\A#{issue.iid}-a+\z/ + end + end end From ad97bebfed2e65951c7dc39ee80b32089a032804 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 17 Feb 2016 07:11:48 +0100 Subject: [PATCH 022/153] Enhance new branch button on an issue --- .../projects/branches_controller.rb | 6 ++ app/models/issue.rb | 11 ++- app/services/merge_requests/build_service.rb | 12 ++- app/services/system_note_service.rb | 9 ++ .../projects/issues/_new_branch.html.haml | 4 +- .../projects/branches_controller_spec.rb | 91 ++++++++++++------- spec/models/issue_spec.rb | 4 - spec/services/system_note_service_spec.rb | 12 +++ 8 files changed, 99 insertions(+), 50 deletions(-) diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index cf68f50b1f..bcbd3aa5d9 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -27,6 +27,12 @@ class Projects::BranchesController < Projects::ApplicationController result = CreateBranchService.new(project, current_user). execute(branch_name, ref) + if params[:issue_id] + issue = Issue.where(id: params[:issue_id], project: @project).limit(1).first + + SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) + end + if result[:status] == :success @branch = result[:branch] redirect_to namespace_project_tree_path(@project.namespace, @project, diff --git a/app/models/issue.rb b/app/models/issue.rb index 243d9a5db6..3b6bff6c57 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -95,7 +95,7 @@ class Issue < ActiveRecord::Base end def related_branches - self.project.repository.branch_names.select { |branch| /\A#{iid}-/ =~ branch } + self.project.repository.branch_names.select { |branch| branch.start_with? "#{iid}-" } end # Reset issue events cache @@ -126,12 +126,13 @@ class Issue < ActiveRecord::Base end def to_branch_name - "#{iid}-#{title.parameterize}"[0,25] + "#{iid}-#{title.parameterize}" end - def new_branch_button?(current_user) + def can_be_worked_on?(current_user) !self.closed? && - referenced_merge_requests(current_user).empty? && - related_branches.empty? + !self.project.forked? && + referenced_merge_requests(current_user).none? { |mr| mr.closes_issue?(self) } && + related_branches.empty? end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 8f1b735b8d..fa34753c4f 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -51,11 +51,15 @@ module MergeRequests # be interpreted as the use wants to close that issue on this project # Pattern example: 112-fix-mep-mep # Will lead to appending `Closes #112` to the description - if merge_request.source_branch =~ /\A\d+-/ - closes_issue = "Closes ##{Regexp.last_match(0)[0...-1]}" - closes_issue.prepend("\n") if merge_request.description.present? + if match = merge_request.source_branch.match(/\A(\d+)-/) + iid = match[1] + closes_issue = "Closes ##{iid}" - merge_request.description << closes_issue + if merge_request.description.present? + merge_request.description << closes_issue.prepend("\n") + else + merge_request.description = closes_issue + end end merge_request diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 58a861ee08..751815c5b1 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -207,6 +207,15 @@ class SystemNoteService create_note(noteable: noteable, project: project, author: author, note: body) end + # Called when a branch is created from the 'new branch' button on a issue + # Example note text: + # + # "Started branch `201-issue-branch-button`" + def self.new_issue_branch(issue, project, author, branch) + body = "Started branch `#{branch}`" + create_note(noteable: issue, project: project, author: author, note: body) + end + # Called when a Mentionable references a Noteable # # noteable - Noteable object being referenced diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 4d5fa61a91..a6b97b90e6 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,5 +1,5 @@ -- if current_user && can?(current_user, :push_code, @project) && @issue.new_branch_button?(current_user) +- if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) .pull-right - = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name), method: :post, class: 'btn' do + = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_id: @issue.id), method: :post, class: 'btn', title: @issue.to_branch_name do = icon('code-fork') New Branch diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 8e06d4bdc7..b509714e75 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -17,49 +17,70 @@ describe Projects::BranchesController do describe "POST create" do render_views - before do - post :create, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - branch_name: branch, - ref: ref - end + context "on creation of a new branch" do + before do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + ref: ref + end - context "valid branch name, valid source" do - let(:branch) { "merge_branch" } - let(:ref) { "master" } - it 'redirects' do - expect(subject). - to redirect_to("/#{project.path_with_namespace}/tree/merge_branch") + context "valid branch name, valid source" do + let(:branch) { "merge_branch" } + let(:ref) { "master" } + it 'redirects' do + expect(subject). + to redirect_to("/#{project.path_with_namespace}/tree/merge_branch") + end + end + + context "invalid branch name, valid ref" do + let(:branch) { "" } + let(:ref) { "master" } + it 'redirects' do + expect(subject). + to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');") + end + end + + context "valid branch name, invalid ref" do + let(:branch) { "merge_branch" } + let(:ref) { "" } + it { is_expected.to render_template('new') } + end + + context "invalid branch name, invalid ref" do + let(:branch) { "" } + let(:ref) { "" } + it { is_expected.to render_template('new') } + end + + context "valid branch name with encoded slashes" do + let(:branch) { "feature%2Ftest" } + let(:ref) { "" } + it { is_expected.to render_template('new') } + it { project.repository.branch_names.include?('feature/test') } end end - context "invalid branch name, valid ref" do - let(:branch) { "" } - let(:ref) { "master" } + describe "created from the new branch button on issues" do + let(:branch) { "1-feature-branch" } + let!(:issue) { create(:issue) } + + before do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + issue_id: issue.id + end + it 'redirects' do expect(subject). - to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');") + to redirect_to("/#{project.path_with_namespace}/tree/1-feature-branch") end - end - context "valid branch name, invalid ref" do - let(:branch) { "merge_branch" } - let(:ref) { "" } - it { is_expected.to render_template('new') } - end - - context "invalid branch name, invalid ref" do - let(:branch) { "" } - let(:ref) { "" } - it { is_expected.to render_template('new') } - end - - context "valid branch name with encoded slashes" do - let(:branch) { "feature%2Ftest" } - let(:ref) { "" } - it { is_expected.to render_template('new') } - it { project.repository.branch_names.include?('feature/test')} end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index d572a71cf4..97b2db2fba 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -144,10 +144,6 @@ describe Issue, models: true do describe "#to_branch_name" do let(:issue) { build(:issue, title: 'a' * 30) } - it "is expected not to exceed 25 chars" do - expect(issue.to_branch_name.length).to eq 25 - end - it "starts with the issue iid" do expect(issue.to_branch_name).to match /\A#{issue.iid}-a+\z/ end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 5dcc39f5fd..2730b42c61 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -280,6 +280,18 @@ describe SystemNoteService, services: true do end end + describe '.new_issue_branch' do + subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") } + + it_behaves_like 'a system note' + + context 'when a branch is created from the new branch button' do + it 'sets the note text' do + expect(subject.note).to eq 'Started branch 1-mepmep' + end + end + end + describe '.cross_reference' do subject { described_class.cross_reference(noteable, mentioner, author) } From e831a3b869cbb82e9a4294a5f9309ba56df46589 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 19 Feb 2016 11:58:02 +0100 Subject: [PATCH 023/153] Link in the note when started a new branch from an issue --- app/services/system_note_service.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 751815c5b1..b65ac47bce 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -212,7 +212,10 @@ class SystemNoteService # # "Started branch `201-issue-branch-button`" def self.new_issue_branch(issue, project, author, branch) - body = "Started branch `#{branch}`" + h = Gitlab::Application.routes.url_helpers + link = "#{Gitlab.config.gitlab.url}#{h.namespace_project_compare_path(project.namespace, project, from: project.default_branch, to: branch)}" + + body = "Started branch [#{branch}](#{link})" create_note(noteable: issue, project: project, author: author, note: body) end From 2b97c921196a7be904bfe4f0a31347c3583c9e88 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 22 Feb 2016 09:20:04 +0100 Subject: [PATCH 024/153] Incorporate review --- CHANGELOG | 3 +- app/assets/stylesheets/pages/issues.scss | 7 +-- .../projects/branches_controller.rb | 7 ++- app/models/issue.rb | 19 ++++--- app/services/system_note_service.rb | 2 +- .../projects/issues/_new_branch.html.haml | 2 +- .../issues/_related_branches.html.haml | 2 +- .../projects/branches_controller_spec.rb | 19 +++++-- .../features/issues/new_branch_button_spec.rb | 50 +++++++++++++++++++ spec/models/issue_spec.rb | 9 ++++ 10 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 spec/features/issues/new_branch_button_spec.rb diff --git a/CHANGELOG b/CHANGELOG index fe65e7b34f..563a596640 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.5.3 - Show commit message in JIRA mention comment - Makes issue page and merge request page usable on mobile browsers. - Improved UI for profile settings + - New branch button appears on issues where applicable v 8.5.2 - Fix sidebar overlapping content when screen width was below 1200px @@ -175,8 +176,6 @@ v 8.5.0 - Add label description (Nuttanart Pornprasitsakul) - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Add Todos - - User project limit is reached notice is hidden if the projects limit is zero - - New branch button appears on issues where applicable (Zeger-Jan van de Weg) v 8.4.5 - No CE-specific changes diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 5f1810cbaf..0f2e14d1a2 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -49,12 +49,7 @@ form.edit-issue { margin: 0; } -.related-branches-title { - font-size: 16px; - font-weight: 600; -} - -.merge-requests-title { +.merge-requests-title, .related-branches-title { font-size: 16px; font-weight: 600; } diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index bcbd3aa5d9..43ea717cbd 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -27,10 +27,9 @@ class Projects::BranchesController < Projects::ApplicationController result = CreateBranchService.new(project, current_user). execute(branch_name, ref) - if params[:issue_id] - issue = Issue.where(id: params[:issue_id], project: @project).limit(1).first - - SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) + if params[:issue_iid] + issue = @project.issues.find_by(iid: params[:issue_iid]) + SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue end if result[:status] == :success diff --git a/app/models/issue.rb b/app/models/issue.rb index 3b6bff6c57..ec275d5f5b 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -87,11 +87,16 @@ class Issue < ActiveRecord::Base end def referenced_merge_requests(current_user = nil) - Gitlab::ReferenceExtractor.lazily do - [self, *notes].flat_map do |note| - note.all_references(current_user).merge_requests - end - end.sort_by(&:iid) + if defined?(@referenced_merge_requests) + @referenced_merge_requests[current_user] ||= Gitlab::ReferenceExtractor.lazily do + [self, *notes].flat_map do |note| + note.all_references(current_user).merge_requests + end + end.sort_by(&:iid).uniq + else + @referenced_merge_requests = {} + referenced_merge_requests(current_user) + end end def related_branches @@ -132,7 +137,7 @@ class Issue < ActiveRecord::Base def can_be_worked_on?(current_user) !self.closed? && !self.project.forked? && - referenced_merge_requests(current_user).none? { |mr| mr.closes_issue?(self) } && - related_branches.empty? + self.related_branches.empty? && + self.referenced_merge_requests(current_user).empty? end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index b65ac47bce..5ea7d405e4 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -213,7 +213,7 @@ class SystemNoteService # "Started branch `201-issue-branch-button`" def self.new_issue_branch(issue, project, author, branch) h = Gitlab::Application.routes.url_helpers - link = "#{Gitlab.config.gitlab.url}#{h.namespace_project_compare_path(project.namespace, project, from: project.default_branch, to: branch)}" + link = "#{h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)}" body = "Started branch [#{branch}](#{link})" create_note(noteable: issue, project: project, author: author, note: body) diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index a6b97b90e6..e66e4669d4 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,5 +1,5 @@ - if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) .pull-right - = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_id: @issue.id), method: :post, class: 'btn', title: @issue.to_branch_name do + = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn', title: @issue.to_branch_name do = icon('code-fork') New Branch diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index 5638766198..b10cd03515 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -11,5 +11,5 @@ = render_ci_status(ci_commit) %span.related-branch-info %strong - = link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch) do + = link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "label-branch" do = branch diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index b509714e75..98ae424ed7 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -66,21 +66,30 @@ describe Projects::BranchesController do describe "created from the new branch button on issues" do let(:branch) { "1-feature-branch" } - let!(:issue) { create(:issue) } + let!(:issue) { create(:issue, project: project) } - before do + + it 'redirects' do post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, branch_name: branch, - issue_id: issue.id - end + issue_iid: issue.iid - it 'redirects' do expect(subject). to redirect_to("/#{project.path_with_namespace}/tree/1-feature-branch") end + it 'posts a system note' do + expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch") + + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + issue_iid: issue.iid + end + end end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb new file mode 100644 index 0000000000..f47d7a5471 --- /dev/null +++ b/spec/features/issues/new_branch_button_spec.rb @@ -0,0 +1,50 @@ +require 'rails_helper' + +feature 'Start new branch from an issue', feature: true do + let!(:project) { create(:project) } + let!(:issue) { create(:issue, project: project) } + let!(:user) { create(:user)} + + context "for team members" do + before do + project.team << [user, :master] + login_as(user) + end + + it 'shown the new branch button', js: false do + visit namespace_project_issue_path(project.namespace, project, issue) + + expect(page).to have_link "New Branch" + end + + context "when there is a referenced merge request" do + let(:note) do + create(:note, :on_issue, :system, project: project, + note: "mentioned in !#{referenced_mr.iid}") + end + let(:referenced_mr) do + create(:merge_request, source_project: project, + target_project: project, + description: "Fixes ##{issue.iid}") + end + + before do + issue.notes << note + + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it "hides the new branch button", js: true do + expect(page).not_to have_link "New Branch" + expect(page).to have_content /1 Related Merge Request/ + end + end + end + + context "for visiters" do + it 'no button is shown', js: false do + visit namespace_project_issue_path(project.namespace, project, issue) + expect(page).not_to have_link "New Branch" + end + end +end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 97b2db2fba..2ccdec1eef 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -130,6 +130,15 @@ describe Issue, models: true do end end + describe '#related_branches' do + it "should " do + allow(subject.project.repository).to receive(:branch_names). + and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name]) + + expect(subject.related_branches).to eq [subject.to_branch_name] + end + end + it_behaves_like 'an editable mentionable' do subject { create(:issue) } From e03d0b28920bdc43ec321efa33f19777488a9187 Mon Sep 17 00:00:00 2001 From: connorshea Date: Tue, 15 Mar 2016 13:56:28 -0600 Subject: [PATCH 025/153] Follow the CSS Style Guide rules for HEX colors. Use lowercase letters and shorthand values for HEX colors where possible. Discussed in #14299. --- app/assets/stylesheets/framework/buttons.scss | 10 +- .../stylesheets/framework/calendar.scss | 8 +- app/assets/stylesheets/framework/callout.scss | 2 +- app/assets/stylesheets/framework/common.scss | 18 +-- .../stylesheets/framework/dropdowns.scss | 2 +- app/assets/stylesheets/framework/files.scss | 4 +- app/assets/stylesheets/framework/forms.scss | 4 +- .../stylesheets/framework/gitlab-theme.scss | 28 ++--- app/assets/stylesheets/framework/header.scss | 10 +- .../stylesheets/framework/issue_box.scss | 2 +- app/assets/stylesheets/framework/jquery.scss | 18 +-- .../stylesheets/framework/markdown_area.scss | 4 +- app/assets/stylesheets/framework/mixins.scss | 2 +- app/assets/stylesheets/framework/mobile.scss | 4 +- app/assets/stylesheets/framework/nav.scss | 4 +- app/assets/stylesheets/framework/selects.scss | 2 +- app/assets/stylesheets/framework/sidebar.scss | 4 +- .../stylesheets/framework/tw_bootstrap.scss | 2 +- .../framework/tw_bootstrap_variables.scss | 6 +- .../stylesheets/framework/typography.scss | 6 +- .../stylesheets/framework/variables.scss | 104 +++++++++--------- app/assets/stylesheets/highlight/dark.scss | 18 +-- app/assets/stylesheets/highlight/monokai.scss | 2 +- .../stylesheets/highlight/solarized_dark.scss | 2 +- .../highlight/solarized_light.scss | 2 +- app/assets/stylesheets/highlight/white.scss | 80 +++++++------- app/assets/stylesheets/pages/builds.scss | 2 +- app/assets/stylesheets/pages/commits.scss | 4 +- app/assets/stylesheets/pages/diff.scss | 6 +- app/assets/stylesheets/pages/editor.scss | 4 +- app/assets/stylesheets/pages/events.scss | 6 +- app/assets/stylesheets/pages/graph.scss | 4 +- app/assets/stylesheets/pages/issuable.scss | 4 +- app/assets/stylesheets/pages/issues.scss | 12 +- app/assets/stylesheets/pages/login.scss | 4 +- .../stylesheets/pages/merge_requests.scss | 4 +- app/assets/stylesheets/pages/note_form.scss | 2 +- app/assets/stylesheets/pages/notes.scss | 10 +- app/assets/stylesheets/pages/projects.scss | 12 +- app/assets/stylesheets/pages/runners.scss | 2 +- app/assets/stylesheets/pages/search.scss | 2 +- app/assets/stylesheets/pages/status.scss | 2 +- app/assets/stylesheets/pages/todos.scss | 2 +- app/assets/stylesheets/pages/ui_dev_kit.scss | 2 +- app/assets/stylesheets/pages/xterm.scss | 52 ++++----- 45 files changed, 242 insertions(+), 242 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 8d475137b0..76611efb78 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -37,23 +37,23 @@ } @mixin btn-green { - @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #FFFFFF); + @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #fff); } @mixin btn-blue { - @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF); + @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #fff); } @mixin btn-blue-medium { - @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #FFFFFF); + @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #fff); } @mixin btn-orange { - @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF); + @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #fff); } @mixin btn-red { - @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #FFFFFF); + @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #fff); } @mixin btn-gray { diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 580012abd7..e3192823a1 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -33,19 +33,19 @@ } .q2 { - fill: #ACD5F2 !important; + fill: #acd5f2 !important; } .q3 { - fill: #7FA8D1 !important; + fill: #7fa8d1 !important; } .q4 { - fill: #49729B !important; + fill: #49729b !important; } .q5 { - fill: #254E77 !important; + fill: #254e77 !important; } .domain-background { diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index 20a9bfb981..da7bab74a3 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -39,6 +39,6 @@ } .bs-callout-success { background-color: #dff0d8; - border-color: #5cA64d; + border-color: #5ca64d; color: #3c763d; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index ff551f151f..180926b3b9 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -1,6 +1,6 @@ /** COLORS **/ .cgray { color: $gl-gray; } -.clgray { color: #BBB } +.clgray { color: #bbb } .cred { color: $gl-text-red; } .cgreen { color: $gl-text-green; } .cdark { color: #444 } @@ -51,7 +51,7 @@ pre { } &.well-pre { - border: 1px solid #EEE; + border: 1px solid #eee; background: #f9f9f9; border-radius: 0; color: #555; @@ -103,7 +103,7 @@ span.update-author { } .user-mention { - color: #2FA0BB; + color: #2fa0bb; font-weight: bold; } @@ -187,9 +187,9 @@ li.note { .error-message { padding: 10px; - background: #C67; + background: #c67; margin: 0; - color: #FFF; + color: #fff; a { color: #fff; @@ -200,7 +200,7 @@ li.note { .browser-alert { padding: 10px; text-align: center; - background: #C67; + background: #c67; color: #fff; font-weight: bold; a { @@ -271,7 +271,7 @@ img.emoji { table { td.permission-x { - background: #D9EDF7 !important; + background: #d9edf7 !important; text-align: center; } } @@ -280,7 +280,7 @@ table { float: left; text-align: center; font-size: 32px; - color: #AAA; + color: #aaa; width: 60px; } @@ -347,7 +347,7 @@ table { .profiler-button, .profiler-controls { - border-color: #EEE !important; + border-color: #eee !important; } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 5b647fc617..ffa5887807 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -261,7 +261,7 @@ position: absolute; top: 10px; right: 10px; - color: #C7C7C7; + color: #c7c7c7; font-size: 12px; pointer-events: none; } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index b034a4882c..116cc0df81 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -84,7 +84,7 @@ &.blob-no-preview { background: #eee; - text-shadow: 0 1px 2px #FFF; + text-shadow: 0 1px 2px #fff; padding: 100px 0; } @@ -124,7 +124,7 @@ } td.line-numbers { float: none; - border-left: 1px solid #DDD; + border-left: 1px solid #ddd; } td.lines { padding: 0; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 6c08005812..4cb4129b71 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -41,8 +41,8 @@ input[type='search'].search-input { } input[type='text'].danger { - background: #F2DEDE!important; - border-color: #D66; + background: #f2dede!important; + border-color: #d66; text-shadow: 0 1px 1px #fff } diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 12cef6f8ea..2a4cf4fc33 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -23,13 +23,13 @@ &:hover { background-color: $color-darker; a { - color: #FFF; + color: #fff; } } } .collapse-nav a { - color: #FFF; + color: #fff; background: $color; } @@ -42,7 +42,7 @@ &:hover { background-color: $color-dark; - color: #FFF; + color: #fff; text-decoration: none; } } @@ -71,7 +71,7 @@ } &.active a { - color: #FFF; + color: #fff; background: $color-dark; &.no-highlight { @@ -79,42 +79,42 @@ } i { - color: #FFF + color: #fff } } } } } -$theme-blue: #2980B9; +$theme-blue: #2980b9; $theme-charcoal: #333c47; -$theme-graphite: #888888; +$theme-graphite: #888; $theme-gray: #373737; $theme-green: #019875; -$theme-violet: #554488; +$theme-violet: #548; body { &.ui_blue { - @include gitlab-theme(#BECDE9, $theme-blue, #1970A9, #096099); + @include gitlab-theme(#becde9, $theme-blue, #1970a9, #096099); } &.ui_charcoal { - @include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272D); + @include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272d); } &.ui_graphite { - @include gitlab-theme(#CCCCCC, $theme-graphite, #777777, #666666); + @include gitlab-theme(#ccc, $theme-graphite, #777, #666); } &.ui_gray { - @include gitlab-theme(#979797, $theme-gray, #272727, #222222); + @include gitlab-theme(#979797, $theme-gray, #272727, #222); } &.ui_green { - @include gitlab-theme(#AADDCC, $theme-green, #018865, #017855); + @include gitlab-theme(#adc, $theme-green, #018865, #017855); } &.ui_violet { - @include gitlab-theme(#9988CC, $theme-violet, #443366, #332255); + @include gitlab-theme(#98c, $theme-violet, #436, #325); } } \ No newline at end of file diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 4c4033e3ae..5f70cd399f 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -7,8 +7,8 @@ header { &.navbar-empty { height: 58px; - background: #FFF; - border-bottom: 1px solid #EEE; + background: #fff; + border-bottom: 1px solid #eee; .center-logo { margin: 11px 0; @@ -28,7 +28,7 @@ header { min-height: $header-height; background-color: #fff; border: none; - border-bottom: 1px solid #EEE; + border-bottom: 1px solid #eee; .container-fluid { width: 100% !important; @@ -47,7 +47,7 @@ header { text-align: center; &:hover, &:focus, &:active { - background-color: #FFF; + background-color: #fff; } } @@ -59,7 +59,7 @@ header { right: 2px; &:hover { - background-color: #EEE; + background-color: #eee; } &.active { color: #7f8fa4; diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 77a00586b2..7f7b7c806e 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -20,7 +20,7 @@ display: block; float: left; margin-right: 10px; - color: #FFF; + color: #fff; font-size: $gl-font-size; line-height: 25px; diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 0cdcd923b3..525ed81b05 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -3,13 +3,13 @@ font-size: $font-size-base; &.ui-datepicker-inline { - border: 1px solid #DDD; + border: 1px solid #ddd; padding: 10px; width: 270px; .ui-datepicker-header { - background: #FFF; - border-color: #DDD; + background: #fff; + border-color: #ddd; } .ui-datepicker-calendar td a { @@ -19,7 +19,7 @@ } &.ui-autocomplete { - border-color: #DDD; + border-color: #ddd; padding: 0; margin-top: 2px; z-index: 1001; @@ -30,20 +30,20 @@ } .ui-state-default { - border: 1px solid #FFF; - background: #FFF; + border: 1px solid #fff; + background: #fff; color: #777; } .ui-state-highlight { - border: 1px solid #EEE; - background: #EEE; + border: 1px solid #eee; + background: #eee; } .ui-state-active { border: 1px solid $gl-primary; background: $gl-primary; - color: #FFF; + color: #fff; } .ui-state-hover, diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 1d8611b04d..8328aac4e7 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -71,7 +71,7 @@ } .md-preview-holder { - background: #FFF; + background: #fff; border: 1px solid #ddd; min-height: 169px; padding: 5px; @@ -80,7 +80,7 @@ .markdown-area { @include border-radius(0); - background: #FFF; + background: #fff; border: 1px solid #ddd; min-height: 140px; max-height: 500px; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1d5000fe38..4b59c758f1 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -75,7 +75,7 @@ @include clearfix; padding: 10px 0; - border-bottom: 1px solid #EEE; + border-bottom: 1px solid #eee; display: block; margin: 0px; diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 3bfac2ad9b..ec10a70cf0 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -131,9 +131,9 @@ right: 0px; top: 30%; padding: 5px 15px; - background: #EEE; + background: #eee; font-size: 20px; color: #777; z-index: 100; - @include box-shadow(0 1px 2px #DDD); + @include box-shadow(0 1px 2px #ddd); } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index b2fbc95e04..b53872bf67 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -26,7 +26,7 @@ } &.active a { - color: #000000; + color: #000; border-bottom: 2px solid #4688f1; } @@ -41,7 +41,7 @@ .top-area { @include clearfix; - border-bottom: 1px solid #EEE; + border-bottom: 1px solid #eee; .nav-text { padding-top: 16px; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 7bf04e4ad7..cbcfef8a69 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -229,7 +229,7 @@ .namespace-result { .namespace-kind { - color: #AAA; + color: #aaa; font-weight: normal; } .namespace-path { diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 26df9acd2a..29570f2ce3 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -47,7 +47,7 @@ width: 100%; .container-fluid { - background: #FFF; + background: #fff; padding: 0 $gl-padding; &.container-blank { @@ -103,7 +103,7 @@ } &:hover { - background-color: #EEE; + background-color: #eee; } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index ddf76704a5..9946dd3e9b 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -95,7 +95,7 @@ } &.label-inverse { - background-color: #333333; + background-color: #333; } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index b1b8295411..f63ac03323 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -57,7 +57,7 @@ $component-active-bg: $brand-info; $input-color: $text-color; $input-border: #e7e9ed; -$input-border-focus: #7F8FA4; +$input-border-focus: #7f8fa4; $legend-color: $text-color; @@ -125,8 +125,8 @@ $panel-inner-border: $border-color; // //## -$well-bg: #F9F9F9; -$well-border: #EEE; +$well-bg: #f9f9f9; +$well-border: #eee; //== Code // diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 9381cb3281..6ff8852c2f 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -27,13 +27,13 @@ line-height: 10px; color: #555; vertical-align: middle; - background-color: #FCFCFC; + background-color: #fcfcfc; border-width: 1px; border-style: solid; - border-color: #CCC #CCC #BBB; + border-color: #ccc #ccc #bbb; border-image: none; border-radius: 3px; - box-shadow: 0px -1px 0px #BBB inset; + box-shadow: 0px -1px 0px #bbb inset; } h1 { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index d491d01a3c..3447c558e8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -1,8 +1,8 @@ $row-hover: #f4f8fe; -$gl-text-color: #54565B; -$gl-text-green: #4A2; -$gl-text-red: #D12F19; -$gl-text-orange: #D90; +$gl-text-color: #54565b; +$gl-text-green: #4a2; +$gl-text-red: #d12f19; +$gl-text-orange: #d90; $gl-header-color: #323232; $gl-link-color: #333c48; $md-text-color: #444; @@ -30,24 +30,24 @@ $gl-vert-padding: 6px; $gl-padding-top:10px; $gl-avatar-size: 40px; $secondary-text: #7f8fa4; -$error-exclamation-point: #E62958; +$error-exclamation-point: #e62958; $border-radius-default: 3px; -$list-title-color: #333333; -$list-text-color: #555555; +$list-title-color: #333; +$list-text-color: #555; -$btn-transparent-color: #8F8F8F; +$btn-transparent-color: #8f8f8f; -$ssh-key-icon-color: #8F8F8F; +$ssh-key-icon-color: #8f8f8f; $ssh-key-icon-size: 18px; -$provider-btn-group-border: #E5E5E5; -$provider-btn-not-active-color: #4688F1; +$provider-btn-group-border: #e5e5e5; +$provider-btn-not-active-color: #4688f1; /* * Color schema */ -$white-light: #FFFFFF; +$white-light: #fff; $white-normal: #ededed; $white-dark: #ededed; @@ -57,55 +57,55 @@ $gray-dark: #ededed; $gray-darkest: #c9c9c9; $green-light: #38ae67; -$green-normal: #2FAA60; -$green-dark: #2CA05B; +$green-normal: #2faa60; +$green-dark: #2ca05b; -$blue-light: #2EA8E5; -$blue-normal: #2D9FD8; -$blue-dark: #2897CE; +$blue-light: #2ea8e5; +$blue-normal: #2d9fd8; +$blue-dark: #2897ce; -$blue-medium-light: #3498CB; -$blue-medium: #2F8EBF; -$blue-medium-dark: #2D86B4; +$blue-medium-light: #3498cb; +$blue-medium: #2f8ebf; +$blue-medium-dark: #2d86b4; $orange-light: rgba(252, 109, 38, 0.80); -$orange-normal: #E75E40; -$orange-dark: #CE5237; +$orange-normal: #e75e40; +$orange-dark: #ce5237; -$red-light: #F06559; -$red-normal: #E52C5A; -$red-dark: #D22852; +$red-light: #f06559; +$red-normal: #e52c5a; +$red-dark: #d22852; -$border-white-light: #F1F2F4; -$border-white-normal: #D6DAE2; -$border-white-dark: #C6CACF; +$border-white-light: #f1f2f4; +$border-white-normal: #d6dae2; +$border-white-dark: #c6cacf; $border-gray-light: rgba(0, 0, 0, 0.06); $border-gray-normal: rgba(0, 0, 0, 0.10);; -$border-gray-dark: #C6CACF; +$border-gray-dark: #c6cacf; -$border-green-light: #2FAA60; -$border-green-normal: #2CA05B; +$border-green-light: #2faa60; +$border-green-normal: #2ca05b; $border-green-dark: #279654; -$border-blue-light: #2D9FD8; -$border-blue-normal: #2897CE; -$border-blue-dark: #258DC1; +$border-blue-light: #2d9fd8; +$border-blue-normal: #2897ce; +$border-blue-dark: #258dc1; $border-orange-light: #fc6d26; -$border-orange-normal: #CE5237; -$border-orange-dark: #C14E35; +$border-orange-normal: #ce5237; +$border-orange-dark: #c14e35; -$border-red-light: #F24F41; -$border-red-normal: #D22852; -$border-red-dark: #CA264F; +$border-red-light: #f24f41; +$border-red-normal: #d22852; +$border-red-dark: #ca264f; -$help-well-bg: #FAFAFA; -$help-well-border: #E5E5E5; +$help-well-bg: #fafafa; +$help-well-border: #e5e5e5; -$warning-message-bg: #FBF2D9; -$warning-message-color: #9E8E60; -$warning-message-border: #F0E2BB; +$warning-message-bg: #fbf2d9; +$warning-message-color: #9e8e60; +$warning-message-border: #f0e2bb; /* header */ $light-grey-header: #faf9f9; @@ -143,23 +143,23 @@ $dropdown-border-color: rgba(#000, .1); $dropdown-shadow-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1); $dropdown-header-color: #959494; -$dropdown-caret-color: #54565B; -$dropdown-title-btn-color: #BFBFBF; -$dropdown-input-color: #C7C7C7; +$dropdown-caret-color: #54565b; +$dropdown-title-btn-color: #bfbfbf; +$dropdown-input-color: #c7c7c7; $dropdown-input-focus-border: rgb(58, 171, 240); $dropdown-input-focus-shadow: rgba(#000, .2); $dropdown-loading-bg: rgba(#fff, .6); $dropdown-toggle-bg: #fff; $dropdown-toggle-color: #626262; -$dropdown-toggle-border-color: #EAEAEA; +$dropdown-toggle-border-color: #eaeaea; $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); -$dropdown-toggle-icon-color: #C4C4C4; +$dropdown-toggle-icon-color: #c4c4c4; $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; /* * Award emoji */ -$award-emoji-menu-bg: #FFF; -$award-emoji-menu-border: #F1F2F4; -$award-emoji-new-btn-icon-color: #DCDCDC; +$award-emoji-menu-bg: #fff; +$award-emoji-menu-border: #f1f2f4; +$award-emoji-new-btn-icon-color: #dcdcdc; diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index b794da2ce9..4767394489 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -43,12 +43,12 @@ // Search result highlight span.highlight_word { background-color: #ffe792 !important; - color: #000000 !important; + color: #000 !important; } .hll { background-color: #373b41 } .c { color: #969896 } /* Comment */ - .err { color: #cc6666 } /* Error */ + .err { color: #c66 } /* Error */ .k { color: #b294bb } /* Keyword */ .l { color: #de935f } /* Literal */ .n { color: #c5c8c6 } /* Name */ @@ -58,7 +58,7 @@ .cp { color: #969896 } /* Comment.Preproc */ .c1 { color: #969896 } /* Comment.Single */ .cs { color: #969896 } /* Comment.Special */ - .gd { color: #cc6666 } /* Generic.Deleted */ + .gd { color: #c66 } /* Generic.Deleted */ .ge { font-style: italic } /* Generic.Emph */ .gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */ .gi { color: #b5bd68 } /* Generic.Inserted */ @@ -77,17 +77,17 @@ .na { color: #81a2be } /* Name.Attribute */ .nb { color: #c5c8c6 } /* Name.Builtin */ .nc { color: #f0c674 } /* Name.Class */ - .no { color: #cc6666 } /* Name.Constant */ + .no { color: #c66 } /* Name.Constant */ .nd { color: #8abeb7 } /* Name.Decorator */ .ni { color: #c5c8c6 } /* Name.Entity */ - .ne { color: #cc6666 } /* Name.Exception */ + .ne { color: #c66 } /* Name.Exception */ .nf { color: #81a2be } /* Name.Function */ .nl { color: #c5c8c6 } /* Name.Label */ .nn { color: #f0c674 } /* Name.Namespace */ .nx { color: #81a2be } /* Name.Other */ .py { color: #c5c8c6 } /* Name.Property */ .nt { color: #8abeb7 } /* Name.Tag */ - .nv { color: #cc6666 } /* Name.Variable */ + .nv { color: #c66 } /* Name.Variable */ .ow { color: #8abeb7 } /* Operator.Word */ .w { color: #c5c8c6 } /* Text.Whitespace */ .mf { color: #de935f } /* Literal.Number.Float */ @@ -106,8 +106,8 @@ .s1 { color: #b5bd68 } /* Literal.String.Single */ .ss { color: #b5bd68 } /* Literal.String.Symbol */ .bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */ - .vc { color: #cc6666 } /* Name.Variable.Class */ - .vg { color: #cc6666 } /* Name.Variable.Global */ - .vi { color: #cc6666 } /* Name.Variable.Instance */ + .vc { color: #c66 } /* Name.Variable.Class */ + .vg { color: #c66 } /* Name.Variable.Global */ + .vi { color: #c66 } /* Name.Variable.Instance */ .il { color: #de935f } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 9098e07adc..806401c21a 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -43,7 +43,7 @@ // Search result highlight span.highlight_word { background-color: #ffe792 !important; - color: #000000 !important; + color: #000 !important; } .hll { background-color: #49483e } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 8b1a2824f7..6a809d4dfd 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -96,7 +96,7 @@ .m { color: #2aa198 } /* Literal.Number */ .s { color: #2aa198 } /* Literal.String */ .na { color: #93a1a1 } /* Name.Attribute */ - .nb { color: #B58900 } /* Name.Builtin */ + .nb { color: #b58900 } /* Name.Builtin */ .nc { color: #268bd2 } /* Name.Class */ .no { color: #cb4b16 } /* Name.Constant */ .nd { color: #268bd2 } /* Name.Decorator */ diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 7ad89dd2c7..b90c95c62d 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -96,7 +96,7 @@ .m { color: #2aa198 } /* Literal.Number */ .s { color: #2aa198 } /* Literal.String */ .na { color: #586e75 } /* Name.Attribute */ - .nb { color: #B58900 } /* Name.Builtin */ + .nb { color: #b58900 } /* Name.Builtin */ .nc { color: #268bd2 } /* Name.Class */ .no { color: #cb4b16 } /* Name.Constant */ .nd { color: #268bd2 } /* Name.Decorator */ diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 8a091028a6..8c1b0cd84e 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -23,7 +23,7 @@ .line_holder { .diff-line-num { &.old { - background: #ffdddd; + background: #fdd; border-color: #f1c0c0; } @@ -68,66 +68,66 @@ } .hll { background-color: #f8f8f8 } - .c { color: #999988; font-style: italic; } + .c { color: #998; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } .k { font-weight: bold; } .o { font-weight: bold; } - .cm { color: #999988; font-style: italic; } - .cp { color: #999999; font-weight: bold; } - .c1 { color: #999988; font-style: italic; } - .cs { color: #999999; font-weight: bold; font-style: italic; } - .gd { color: #000000; background-color: #ffdddd; } - .gd .x { color: #000000; background-color: #ffaaaa; } + .cm { color: #998; font-style: italic; } + .cp { color: #999; font-weight: bold; } + .c1 { color: #998; font-style: italic; } + .cs { color: #999; font-weight: bold; font-style: italic; } + .gd { color: #000; background-color: #fdd; } + .gd .x { color: #000; background-color: #faa; } .ge { font-style: italic; } - .gr { color: #aa0000; } - .gh { color: #999999; } - .gi { color: #000000; background-color: #ddffdd; } - .gi .x { color: #000000; background-color: #aaffaa; } - .go { color: #888888; } - .gp { color: #555555; } + .gr { color: #a00; } + .gh { color: #999; } + .gi { color: #000; background-color: #dfd; } + .gi .x { color: #000; background-color: #afa; } + .go { color: #888; } + .gp { color: #555; } .gs { font-weight: bold; } .gu { color: #800080; font-weight: bold; } - .gt { color: #aa0000; } + .gt { color: #a00; } .kc { font-weight: bold; } .kd { font-weight: bold; } .kn { font-weight: bold; } .kp { font-weight: bold; } .kr { font-weight: bold; } - .kt { color: #445588; font-weight: bold; } - .m { color: #009999; } - .s { color: #dd1144; } - .n { color: #333333; } + .kt { color: #458; font-weight: bold; } + .m { color: #099; } + .s { color: #d14; } + .n { color: #333; } .na { color: teal; } .nb { color: #0086b3; } - .nc { color: #445588; font-weight: bold; } + .nc { color: #458; font-weight: bold; } .no { color: teal; } .ni { color: purple; } - .ne { color: #990000; font-weight: bold; } - .nf { color: #990000; font-weight: bold; } - .nn { color: #555555; } + .ne { color: #900; font-weight: bold; } + .nf { color: #900; font-weight: bold; } + .nn { color: #555; } .nt { color: navy; } .nv { color: teal; } .ow { font-weight: bold; } - .w { color: #bbbbbb; } - .mf { color: #009999; } - .mh { color: #009999; } - .mi { color: #009999; } - .mo { color: #009999; } - .sb { color: #dd1144; } - .sc { color: #dd1144; } - .sd { color: #dd1144; } - .s2 { color: #dd1144; } - .se { color: #dd1144; } - .sh { color: #dd1144; } - .si { color: #dd1144; } - .sx { color: #dd1144; } + .w { color: #bbb; } + .mf { color: #099; } + .mh { color: #099; } + .mi { color: #099; } + .mo { color: #099; } + .sb { color: #d14; } + .sc { color: #d14; } + .sd { color: #d14; } + .s2 { color: #d14; } + .se { color: #d14; } + .sh { color: #d14; } + .si { color: #d14; } + .sx { color: #d14; } .sr { color: #009926; } - .s1 { color: #dd1144; } + .s1 { color: #d14; } .ss { color: #990073; } - .bp { color: #999999; } + .bp { color: #999; } .vc { color: teal; } .vg { color: teal; } .vi { color: teal; } - .il { color: #009999; } - .gc { color: #999; background-color: #EAF2F5; } + .il { color: #099; } + .gc { color: #999; background-color: #eaf2f5; } } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 75f298019e..201f3e5ca4 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -1,6 +1,6 @@ .build-page { pre.trace { - background: #111111; + background: #111; color: #fff; font-family: $monospace_font; white-space: pre; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 818fd03e2a..2062cd2ebc 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -9,7 +9,7 @@ .lists-separator { margin: 10px 0; - border-color: #DDD; + border-color: #ddd; } .commits-row { @@ -76,7 +76,7 @@ li.commit { .commit-row-description { font-size: 14px; - border-left: 1px solid #EEE; + border-left: 1px solid #eee; padding: 10px 15px; margin: 5px 0 10px 5px; background: #f9f9f9; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index a7925e7954..4739aa0502 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -29,7 +29,7 @@ .diff-content { overflow: auto; overflow-y: hidden; - background: #FFF; + background: #fff; color: #333; .unfold { @@ -118,7 +118,7 @@ background-color: #fff; line-height: 0; img { - border: 1px solid #FFF; + border: 1px solid #fff; background: image-url('trans_bg.gif'); max-width: 100%; } @@ -265,7 +265,7 @@ .view-modes { padding: 10px; text-align: center; - background: #EEE; + background: #eee; ul, li { list-style: none; diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 39d916cd33..43be5e38ba 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -14,9 +14,9 @@ } .cancel-btn { - color: #B94A48; + color: #b94a48; &:hover { - color: #B94A48; + color: #b94a48; } } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 35df9a61c8..1833e2136b 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -83,10 +83,10 @@ .event_icon { position: relative; float: right; - border: 1px solid #EEE; + border: 1px solid #eee; padding: 5px; @include border-radius(5px); - background: #F9F9F9; + background: #f9f9f9; margin-left: 10px; top: -6px; img { @@ -160,7 +160,7 @@ .event-body { margin: 0; - border-left: 2px solid #DDD; + border-left: 2px solid #ddd; padding-left: 10px; } diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss index c3b10d144e..4e5c4ed84b 100644 --- a/app/assets/stylesheets/pages/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss @@ -6,11 +6,11 @@ font-size: 14px; padding: 5px; border-bottom: 1px solid $border-color; - background: #EEE; + background: #eee; } .network-graph { - background: #FFF; + background: #fff; height: 500px; overflow-y: scroll; overflow-x: hidden; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 1310e6ad7c..faa2ebfda7 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -222,7 +222,7 @@ width: 100%; text-align: center; padding-bottom: 10px; - color: #999999; + color: #999; span { display: block; @@ -237,7 +237,7 @@ } i { - color: #999999; + color: #999; } } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 1b686c58ea..73718ff511 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -68,18 +68,18 @@ form.edit-issue { .merge-request, .issue { &.today { - background: #EFE; - border-color: #CEC; + background: #efe; + border-color: #cec; } &.closed { - background: #F9F9F9; - border-color: #E5E5E5; + background: #f9f9f9; + border-color: #e5e5e5; } &.merged { - background: #F9F9F9; - border-color: #E5E5E5; + background: #f9f9f9; + border-color: #e5e5e5; } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 61bec02f6c..6c35a74f08 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -39,7 +39,7 @@ .login-box{ background: #fafafa; border-radius: 10px; - box-shadow: 0 0px 2px #CCC; + box-shadow: 0 0px 2px #ccc; padding: 15px; .login-heading h3 { @@ -90,7 +90,7 @@ } &:active, &:focus { - background-color: #FFF; + background-color: #fff; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 2772623f4b..cee5c47cfb 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -113,7 +113,7 @@ } .mr-widget-footer { - border-top: 1px solid #EEE; + border-top: 1px solid #eee; } .ci-coverage { @@ -222,7 +222,7 @@ margin-bottom: 20px; span { - color: #B2B2B2; + color: #b2b2b2; a { color: $md-link-color; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 158c2a4786..61783ec46a 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -156,7 +156,7 @@ .comment-hints { color: #999; - background: #FFF; + background: #fff; padding: 7px; margin-top: -7px; border: 1px solid $border-color; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index d5f9852ebe..1f631ab03e 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -30,7 +30,7 @@ ul.notes { font-size: 14px; padding-top: 10px; padding-bottom: 10px; - background: #FDFDFD; + background: #fdfdfd; .timeline-icon { .avatar { @@ -129,7 +129,7 @@ ul.notes { hr { // Darken 'whitesmoke' a bit to make it more visible in note bodies - border-color: darken(#F5F5F5, 8%); + border-color: darken(#f5f5f5, 8%); margin: 10px 0; } } @@ -166,7 +166,7 @@ ul.notes { vertical-align: middle; text-align: center; padding: 10px 0; - background: #FFF; + background: #fff; color: $text-color; } &.notes_line2 { @@ -232,7 +232,7 @@ ul.notes { .add-diff-note { margin-top: -4px; @include border-radius(40px); - background: #FFF; + background: #fff; padding: 4px; font-size: 16px; color: $gl-link-color; @@ -249,7 +249,7 @@ ul.notes { &:hover { background: $gl-info; - color: #FFF; + color: #fff; @include show-add-diff-note; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index b1b76edfb3..a29b735eb0 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -140,7 +140,7 @@ left: 1px; margin-top: -9px; border-width: 10px 7px 10px 0; - border-right-color: #FFF; + border-right-color: #fff; } } .count { @@ -165,7 +165,7 @@ margin: 0 11px 0px 4px; &:hover { - background: #FFF; + background: #fff; } } } @@ -462,7 +462,7 @@ pre.light-well { .form-control { @extend .monospace; - background: #FFF; + background: #fff; font-size: 14px; margin-left: -1px; cursor: auto; @@ -472,16 +472,16 @@ pre.light-well { .cannot-be-merged, .cannot-be-merged:hover { - color: #E62958; + color: #e62958; margin-top: 2px; } .private-forks-notice .private-fork-icon { i:nth-child(1) { - color: #2AA056; + color: #2aa056; } i:nth-child(2) { - color: #FFFFFF; + color: #fff; } } diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index a9111a7388..eec22c5dc9 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -1,7 +1,7 @@ .runner-state { padding: 6px 12px; margin-right: 10px; - color: #FFF; + color: #fff; &.runner-state-shared { background: #32b186; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 84234b15c6..b6e4502464 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -16,7 +16,7 @@ margin-bottom: 20px; input { - border-color: #BBB; + border-color: #bbb; font-weight: bold; } } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 4b6ef03567..6f777d1164 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,7 +1,7 @@ .ci-status { padding: 2px 7px; margin-right: 5px; - border: 1px solid #EEE; + border: 1px solid #eee; white-space: nowrap; @include border-radius(4px); diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 479c3c16d4..2ab40941c4 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -89,7 +89,7 @@ .todo-body { margin: 0; - border-left: 2px solid #DDD; + border-left: 2px solid #ddd; padding-left: 10px; } } diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss index 05fa9312ef..587bd6a1e8 100644 --- a/app/assets/stylesheets/pages/ui_dev_kit.scss +++ b/app/assets/stylesheets/pages/ui_dev_kit.scss @@ -7,7 +7,7 @@ .example { &:before { content: "Example"; - color: #BBB; + color: #bbb; } padding: 15px; diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index 9a50096c0d..8886c1dff5 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -2,23 +2,23 @@ // color codes are based on http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg // see also: https://gist.github.com/jasonm23/2868981 - $black: #000000; + $black: #000; $red: #cd0000; $green: #00cd00; $yellow: #cdcd00; - $blue: #0000ee; // according to wikipedia, this is the xterm standard + $blue: #00e; // according to wikipedia, this is the xterm standard //$blue: #1e90ff; // this is used by all the terminals I tried (when configured with the xterm color profile) $magenta: #cd00cd; $cyan: #00cdcd; $white: #e5e5e5; $l-black: #7f7f7f; - $l-red: #ff0000; - $l-green: #00ff00; - $l-yellow: #ffff00; + $l-red: #f00; + $l-green: #0f0; + $l-yellow: #ff0; $l-blue: #5c5cff; - $l-magenta: #ff00ff; - $l-cyan: #00ffff; - $l-white: #ffffff; + $l-magenta: #f0f; + $l-cyan: #0ff; + $l-white: #fff; .term-bold { font-weight: bold; @@ -136,7 +136,7 @@ .xterm-fg-0 { - color: #000000; + color: #000; } .xterm-fg-1 { color: #800000; @@ -163,28 +163,28 @@ color: #808080; } .xterm-fg-9 { - color: #ff0000; + color: #f00; } .xterm-fg-10 { - color: #00ff00; + color: #0f0; } .xterm-fg-11 { - color: #ffff00; + color: #ff0; } .xterm-fg-12 { - color: #0000ff; + color: #00f; } .xterm-fg-13 { - color: #ff00ff; + color: #f0f; } .xterm-fg-14 { - color: #00ffff; + color: #0ff; } .xterm-fg-15 { - color: #ffffff; + color: #fff; } .xterm-fg-16 { - color: #000000; + color: #000; } .xterm-fg-17 { color: #00005f; @@ -199,7 +199,7 @@ color: #0000d7; } .xterm-fg-21 { - color: #0000ff; + color: #00f; } .xterm-fg-22 { color: #005f00; @@ -274,7 +274,7 @@ color: #00d7ff; } .xterm-fg-46 { - color: #00ff00; + color: #0f0; } .xterm-fg-47 { color: #00ff5f; @@ -289,7 +289,7 @@ color: #00ffd7; } .xterm-fg-51 { - color: #00ffff; + color: #0ff; } .xterm-fg-52 { color: #5f0000; @@ -724,7 +724,7 @@ color: #d7ffff; } .xterm-fg-196 { - color: #ff0000; + color: #f00; } .xterm-fg-197 { color: #ff005f; @@ -739,7 +739,7 @@ color: #ff00d7; } .xterm-fg-201 { - color: #ff00ff; + color: #f0f; } .xterm-fg-202 { color: #ff5f00; @@ -814,7 +814,7 @@ color: #ffd7ff; } .xterm-fg-226 { - color: #ffff00; + color: #ff0; } .xterm-fg-227 { color: #ffff5f; @@ -829,7 +829,7 @@ color: #ffffd7; } .xterm-fg-231 { - color: #ffffff; + color: #fff; } .xterm-fg-232 { color: #080808; @@ -850,7 +850,7 @@ color: #3a3a3a; } .xterm-fg-238 { - color: #444444; + color: #444; } .xterm-fg-239 { color: #4e4e4e; @@ -901,6 +901,6 @@ color: #e4e4e4; } .xterm-fg-255 { - color: #eeeeee; + color: #eee; } } From 4bcc0977500af7af070296e2faf817eed10aafb9 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 17 Feb 2016 14:41:33 +0100 Subject: [PATCH 026/153] A worker deletes a user, so the request doesn't time out Fixes #13261 --- CHANGELOG | 1 + app/controllers/admin/users_controller.rb | 2 +- app/workers/delete_user_worker.rb | 10 ++++++++++ spec/workers/delete_user_worker_spec.rb | 22 ++++++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 app/workers/delete_user_worker.rb create mode 100644 spec/workers/delete_user_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 7f076f70c7..6de17a4a09 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -178,6 +178,7 @@ v 8.5.0 v 8.4.5 - No CE-specific changes + - User deletion is now done in the background so the request can not time out v 8.4.4 - Update omniauth-saml gem to 1.4.2 diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 87f4fb455b..3fe33018d7 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -122,7 +122,7 @@ class Admin::UsersController < Admin::ApplicationController DeleteUserService.new(current_user).execute(user) respond_to do |format| - format.html { redirect_to admin_users_path } + format.html { redirect_to admin_users_path, notice: "The user is being deleted." } format.json { head :ok } end end diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb new file mode 100644 index 0000000000..bcbb6d58b4 --- /dev/null +++ b/app/workers/delete_user_worker.rb @@ -0,0 +1,10 @@ +class DeleteUserWorker + include Sidekiq::Worker + + def perform(current_user_id, delete_user_id) + delete_user = User.find(delete_user_id) + current_user = User.find(current_user_id) + + DeleteUserService.new(current_user).execute(delete_user) + end +end diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb new file mode 100644 index 0000000000..73005c507c --- /dev/null +++ b/spec/workers/delete_user_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe DeleteUserWorker do + describe "Deletes a user and all their personal projects" do + let!(:user) { create(:user) } + let!(:current_user) { create(:user) } + let!(:namespace) { create(:namespace, owner: user) } + let!(:project) { create(:project, namespace: namespace) } + + before do + DeleteUserWorker.new.perform(current_user.id, user.id) + end + + it 'deletes all personal projects' do + expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'deletes the user' do + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end From b221d11a25218d3c2ad5304be91c849e1872be6c Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Thu, 25 Feb 2016 09:20:28 +0100 Subject: [PATCH 027/153] Add ability to delete a user with force --- CHANGELOG | 2 +- app/controllers/admin/users_controller.rb | 2 +- app/models/abuse_report.rb | 4 ++-- app/services/delete_user_service.rb | 26 +++++++++++++---------- app/services/destroy_group_service.rb | 4 ++-- app/workers/delete_user_worker.rb | 4 ++-- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6de17a4a09..bdeb9b68bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.5.3 - Show commit message in JIRA mention comment - Makes issue page and merge request page usable on mobile browsers. - Improved UI for profile settings + - User deletion is now done in the background so the request can not time out v 8.5.2 - Fix sidebar overlapping content when screen width was below 1200px @@ -178,7 +179,6 @@ v 8.5.0 v 8.4.5 - No CE-specific changes - - User deletion is now done in the background so the request can not time out v 8.4.4 - Update omniauth-saml gem to 1.4.2 diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 3fe33018d7..3063d299b1 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -119,7 +119,7 @@ class Admin::UsersController < Admin::ApplicationController end def destroy - DeleteUserService.new(current_user).execute(user) + DeleteUserWorker.perform_async(current_user.id, user.id) respond_to do |format| format.html { redirect_to admin_users_path, notice: "The user is being deleted." } diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index cc59aa4e91..155b5f327c 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -19,9 +19,9 @@ class AbuseReport < ActiveRecord::Base validates :message, presence: true validates :user_id, uniqueness: { message: 'has already been reported' } - def remove_user + def remove_user(current_user) user.block - user.destroy + DeleteUserWorker.perform_async(current_user.id, user.id, force: true) end def notify diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb index 173e50c920..390960257c 100644 --- a/app/services/delete_user_service.rb +++ b/app/services/delete_user_service.rb @@ -5,18 +5,22 @@ class DeleteUserService @current_user = current_user end - def execute(user) - if user.solo_owned_groups.present? + def execute(user, options = {}) + if !options[:force] && user.solo_owned_groups.present? user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' - user - else - user.personal_projects.each do |project| - # Skip repository removal because we remove directory with namespace - # that contain all this repositories - ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete! - end - - user.destroy + return user end + + user.solo_owned_groups.each do |group| + DeleteGroupService.new(group, current_user).execute + end + + user.personal_projects.each do |project| + # Skip repository removal because we remove directory with namespace + # that contain all this repositories + ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete! + end + + user.destroy end end diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb index 9189de390a..3c42ac61be 100644 --- a/app/services/destroy_group_service.rb +++ b/app/services/destroy_group_service.rb @@ -6,12 +6,12 @@ class DestroyGroupService end def execute - @group.projects.each do |project| + group.projects.each do |project| # Skip repository removal because we remove directory with namespace # that contain all this repositories ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete! end - @group.destroy + group.destroy end end diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb index bcbb6d58b4..6ff361e4d8 100644 --- a/app/workers/delete_user_worker.rb +++ b/app/workers/delete_user_worker.rb @@ -1,10 +1,10 @@ class DeleteUserWorker include Sidekiq::Worker - def perform(current_user_id, delete_user_id) + def perform(current_user_id, delete_user_id, options = {}) delete_user = User.find(delete_user_id) current_user = User.find(current_user_id) - DeleteUserService.new(current_user).execute(delete_user) + DeleteUserService.new(current_user).execute(delete_user, options.symbolize_keys) end end From d249da1c16bea8f9d340520168c64794c5f78928 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 4 Mar 2016 09:49:23 +0100 Subject: [PATCH 028/153] Specs on force deleting of a user --- app/services/delete_user_service.rb | 2 +- spec/workers/delete_user_worker_spec.rb | 48 +++++++++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb index 390960257c..5f2e4f75b7 100644 --- a/app/services/delete_user_service.rb +++ b/app/services/delete_user_service.rb @@ -12,7 +12,7 @@ class DeleteUserService end user.solo_owned_groups.each do |group| - DeleteGroupService.new(group, current_user).execute + DestroyGroupService.new(group, current_user).execute end user.personal_projects.each do |project| diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb index 73005c507c..bec9aecb48 100644 --- a/spec/workers/delete_user_worker_spec.rb +++ b/spec/workers/delete_user_worker_spec.rb @@ -7,16 +7,52 @@ describe DeleteUserWorker do let!(:namespace) { create(:namespace, owner: user) } let!(:project) { create(:project, namespace: namespace) } - before do - DeleteUserWorker.new.perform(current_user.id, user.id) + context 'no force flag given' do + before do + DeleteUserWorker.new.perform(current_user.id, user.id) + end + + it 'deletes all personal projects' do + expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'deletes the user' do + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + end end - it 'deletes all personal projects' do - expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound) + context "solo owned groups present" do + let(:solo_owned) { create(:group) } + let(:member) { create(:group_member) } + let(:user) { user = member.user } + + before do + solo_owned.group_members = [member] + DeleteUserWorker.new.perform(current_user.id, user.id) + end + + it 'does not delete the user' do + expect(User.find(user.id)).to eq user + end end - it 'deletes the user' do - expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + context "deletions with force" do + let(:solo_owned) { create(:group) } + let(:member) { create(:group_member) } + let(:user) { user = member.user } + + before do + solo_owned.group_members = [member] + DeleteUserWorker.new.perform(current_user.id, user.id, "force" => true) + end + + it 'deletes solo owned groups' do + expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'deletes the user' do + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + end end end end From 4231cfeba5f6de519a8609b8aa8a6880e38c2414 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Mar 2016 12:40:21 +0100 Subject: [PATCH 029/153] Target right release, rebase got it wrong --- CHANGELOG | 1 + spec/workers/delete_user_worker_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bdeb9b68bd..ee9d3cebf4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -49,6 +49,7 @@ v 8.5.5 - Fix pagination for filtered dashboard and explore pages - Fix "Show all" link behavior - Add #upcoming filter to Milestone filter (Tiago Botelho) + - User deletion is now done in the background so the request can not time out v 8.5.4 - Do not cache requests for badges (including builds badge) diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb index bec9aecb48..a98ce6e599 100644 --- a/spec/workers/delete_user_worker_spec.rb +++ b/spec/workers/delete_user_worker_spec.rb @@ -24,7 +24,7 @@ describe DeleteUserWorker do context "solo owned groups present" do let(:solo_owned) { create(:group) } let(:member) { create(:group_member) } - let(:user) { user = member.user } + let(:user) { member.user } before do solo_owned.group_members = [member] @@ -39,7 +39,7 @@ describe DeleteUserWorker do context "deletions with force" do let(:solo_owned) { create(:group) } let(:member) { create(:group_member) } - let(:user) { user = member.user } + let(:user) { member.user } before do solo_owned.group_members = [member] From a09323c9f5df69a60d00278dec64775821751711 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 7 Mar 2016 13:46:37 +0100 Subject: [PATCH 030/153] Fix broken specs --- app/controllers/admin/abuse_reports_controller.rb | 2 +- spec/models/abuse_report_spec.rb | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb index 2463cfa87b..a3f78e3477 100644 --- a/app/controllers/admin/abuse_reports_controller.rb +++ b/app/controllers/admin/abuse_reports_controller.rb @@ -6,7 +6,7 @@ class Admin::AbuseReportsController < Admin::ApplicationController def destroy abuse_report = AbuseReport.find(params[:id]) - abuse_report.remove_user if params[:remove_user] + abuse_report.remove_user(current_user) if params[:remove_user] abuse_report.destroy render nothing: true diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 4799bbaa57..8d16239bcb 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -13,7 +13,8 @@ require 'rails_helper' RSpec.describe AbuseReport, type: :model do - subject { create(:abuse_report) } + subject { create(:abuse_report) } + let(:user) { create(:user) } it { expect(subject).to be_valid } @@ -31,17 +32,13 @@ RSpec.describe AbuseReport, type: :model do describe '#remove_user' do it 'blocks the user' do - report = build(:abuse_report) - - allow(report.user).to receive(:destroy) - - expect { report.remove_user }.to change { report.user.blocked? }.to(true) + expect { subject.remove_user(user) }.to change { subject.user.blocked? }.to(true) end - it 'removes the user' do - report = build(:abuse_report) + it 'lets a worker delete the user' do + expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, force: true) - expect { report.remove_user }.to change { User.count }.by(-1) + subject.remove_user(user) end end From 8077a5f9589e365f58f22015819bff7debcd7299 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Sat, 12 Mar 2016 13:11:30 +0100 Subject: [PATCH 031/153] Clearify who deletes the user --- CHANGELOG | 2 +- app/controllers/admin/abuse_reports_controller.rb | 2 +- app/models/abuse_report.rb | 4 ++-- app/services/delete_user_service.rb | 2 +- spec/models/abuse_report_spec.rb | 7 ++++--- spec/workers/delete_user_worker_spec.rb | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ee9d3cebf4..fd7c7e7bc9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ v 8.6.0 (unreleased) - Add ability to show archived projects on dashboard, explore and group pages - Move group activity to separate page - Continue parameters are checked to ensure redirection goes to the same instance + - User deletion is now done in the background so the request can not time out v 8.5.6 - Obtain a lease before querying LDAP @@ -60,7 +61,6 @@ v 8.5.3 - Show commit message in JIRA mention comment - Makes issue page and merge request page usable on mobile browsers. - Improved UI for profile settings - - User deletion is now done in the background so the request can not time out v 8.5.2 - Fix sidebar overlapping content when screen width was below 1200px diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb index a3f78e3477..e9b0972bdd 100644 --- a/app/controllers/admin/abuse_reports_controller.rb +++ b/app/controllers/admin/abuse_reports_controller.rb @@ -6,7 +6,7 @@ class Admin::AbuseReportsController < Admin::ApplicationController def destroy abuse_report = AbuseReport.find(params[:id]) - abuse_report.remove_user(current_user) if params[:remove_user] + abuse_report.remove_user(deleted_by: current_user) if params[:remove_user] abuse_report.destroy render nothing: true diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index 155b5f327c..b61f512312 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -19,9 +19,9 @@ class AbuseReport < ActiveRecord::Base validates :message, presence: true validates :user_id, uniqueness: { message: 'has already been reported' } - def remove_user(current_user) + def remove_user(deleted_by:) user.block - DeleteUserWorker.perform_async(current_user.id, user.id, force: true) + DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true) end def notify diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb index 5f2e4f75b7..ce79287e35 100644 --- a/app/services/delete_user_service.rb +++ b/app/services/delete_user_service.rb @@ -6,7 +6,7 @@ class DeleteUserService end def execute(user, options = {}) - if !options[:force] && user.solo_owned_groups.present? + if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present? user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' return user end diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 8d16239bcb..ac12ab6c75 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -32,13 +32,14 @@ RSpec.describe AbuseReport, type: :model do describe '#remove_user' do it 'blocks the user' do - expect { subject.remove_user(user) }.to change { subject.user.blocked? }.to(true) + expect { subject.remove_user(deleted_by: user) }.to change { subject.user.blocked? }.to(true) end it 'lets a worker delete the user' do - expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, force: true) + expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, + delete_solo_owned_groups: true) - subject.remove_user(user) + subject.remove_user(deleted_by: user) end end diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb index a98ce6e599..975b0470cb 100644 --- a/spec/workers/delete_user_worker_spec.rb +++ b/spec/workers/delete_user_worker_spec.rb @@ -43,7 +43,7 @@ describe DeleteUserWorker do before do solo_owned.group_members = [member] - DeleteUserWorker.new.perform(current_user.id, user.id, "force" => true) + DeleteUserWorker.new.perform(current_user.id, user.id, "delete_solo_owned_groups" => true) end it 'deletes solo owned groups' do From aff7c92596df8a277bd59411fb77425c91a403ba Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 15 Mar 2016 15:09:55 -0500 Subject: [PATCH 032/153] Typo --- app/views/shared/issuable/_filter.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 42a3c2c3f0..3eb0db276b 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -16,7 +16,7 @@ - if params[:assignee_id] = hidden_field_tag(:assignee_id, params[:assignee_id]) = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee", - placeholder: "Search assignee", data: { any_user: "Any Author", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } }) + placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } }) .filter-item.inline.milestone-filter - if params[:milestone_title] From 48274581551b73575149463be0c050f6b5a564ee Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 15 Mar 2016 20:17:51 +0100 Subject: [PATCH 033/153] Incorporate the review and update spec The feature spec now also tests the absence of the new branch button --- CHANGELOG | 2 +- app/models/issue.rb | 22 +++++++++---------- app/services/system_note_service.rb | 4 ++-- .../features/issues/new_branch_button_spec.rb | 11 +++++----- spec/services/system_note_service_spec.rb | 2 +- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 563a596640..b4a0bea736 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.6.0 (unreleased) - Bump gitlab_git to 9.0.3 (Stan Hu) - Support Golang subpackage fetching (Stan Hu) - Bump Capybara gem to 2.6.2 (Stan Hu) + - New branch button appears on issues where applicable - Contributions to forked projects are included in calendar - Improve the formatting for the user page bio (Connor Shea) - Removed the default password from the initial admin account created during @@ -59,7 +60,6 @@ v 8.5.3 - Show commit message in JIRA mention comment - Makes issue page and merge request page usable on mobile browsers. - Improved UI for profile settings - - New branch button appears on issues where applicable v 8.5.2 - Fix sidebar overlapping content when screen width was below 1200px diff --git a/app/models/issue.rb b/app/models/issue.rb index ec275d5f5b..781298a63b 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -87,20 +87,20 @@ class Issue < ActiveRecord::Base end def referenced_merge_requests(current_user = nil) - if defined?(@referenced_merge_requests) - @referenced_merge_requests[current_user] ||= Gitlab::ReferenceExtractor.lazily do - [self, *notes].flat_map do |note| - note.all_references(current_user).merge_requests - end - end.sort_by(&:iid).uniq - else - @referenced_merge_requests = {} - referenced_merge_requests(current_user) + @referenced_merge_requests ||= {} + @referenced_merge_requests[current_user] ||= begin + Gitlab::ReferenceExtractor.lazily do + [self, *notes].flat_map do |note| + note.all_references(current_user).merge_requests + end + end.sort_by(&:iid).uniq end end def related_branches - self.project.repository.branch_names.select { |branch| branch.start_with? "#{iid}-" } + self.project.repository.branch_names.select do |branch| + branch =~ /\A#{iid}-(?!\d+-stable)/i + end end # Reset issue events cache @@ -138,6 +138,6 @@ class Issue < ActiveRecord::Base !self.closed? && !self.project.forked? && self.related_branches.empty? && - self.referenced_merge_requests(current_user).empty? + self.closed_by_merge_requests(current_user).empty? end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 5ea7d405e4..f09b77c4a5 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -213,9 +213,9 @@ class SystemNoteService # "Started branch `201-issue-branch-button`" def self.new_issue_branch(issue, project, author, branch) h = Gitlab::Application.routes.url_helpers - link = "#{h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)}" + link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) - body = "Started branch [#{branch}](#{link})" + body = "Started branch [`#{branch}`](#{link})" create_note(noteable: issue, project: project, author: author, note: body) end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index f47d7a5471..462cb1f8f6 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -18,14 +18,13 @@ feature 'Start new branch from an issue', feature: true do end context "when there is a referenced merge request" do - let(:note) do + let(:note) do create(:note, :on_issue, :system, project: project, - note: "mentioned in !#{referenced_mr.iid}") + note: "mentioned in !#{referenced_mr.iid}") end - let(:referenced_mr) do - create(:merge_request, source_project: project, - target_project: project, - description: "Fixes ##{issue.iid}") + let(:referenced_mr) do + create(:merge_request, :simple, source_project: project, target_project: project, + description: "Fixes ##{issue.iid}") end before do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 2730b42c61..8e6292014d 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -287,7 +287,7 @@ describe SystemNoteService, services: true do context 'when a branch is created from the new branch button' do it 'sets the note text' do - expect(subject.note).to eq 'Started branch 1-mepmep' + expect(subject.note).to match /\AStarted branch [`1-mepmep`]/ end end end From 29a43373ce9b3676ae7baea61b0ecaf67db67445 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 14 Mar 2016 11:18:03 +0100 Subject: [PATCH 034/153] Specs for the DeleteUserService --- CHANGELOG | 1 - spec/services/delete_user_service_spec.rb | 58 ++++++++++++++++++++++ spec/workers/delete_user_worker_spec.rb | 60 +++++------------------ 3 files changed, 69 insertions(+), 50 deletions(-) create mode 100644 spec/services/delete_user_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index fd7c7e7bc9..2b5c03b875 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,7 +50,6 @@ v 8.5.5 - Fix pagination for filtered dashboard and explore pages - Fix "Show all" link behavior - Add #upcoming filter to Milestone filter (Tiago Botelho) - - User deletion is now done in the background so the request can not time out v 8.5.4 - Do not cache requests for badges (including builds badge) diff --git a/spec/services/delete_user_service_spec.rb b/spec/services/delete_user_service_spec.rb new file mode 100644 index 0000000000..a65938fa03 --- /dev/null +++ b/spec/services/delete_user_service_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe DeleteUserService, services: true do + describe "Deletes a user and all their personal projects" do + let!(:user) { create(:user) } + let!(:current_user) { create(:user) } + let!(:namespace) { create(:namespace, owner: user) } + let!(:project) { create(:project, namespace: namespace) } + + context 'no options are given' do + it 'deletes the user' do + DeleteUserService.new(current_user).execute(user) + + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'will delete the project in the near future' do + expect_any_instance_of(Projects::DestroyService).to receive(:pending_delete!).once + + DeleteUserService.new(current_user).execute(user) + end + end + + context "solo owned groups present" do + let(:solo_owned) { create(:group) } + let(:member) { create(:group_member) } + let(:user) { member.user } + + before do + solo_owned.group_members = [member] + DeleteUserService.new(current_user).execute(user) + end + + it 'does not delete the user' do + expect(User.find(user.id)).to eq user + end + end + + context "deletions with solo owned groups" do + let(:solo_owned) { create(:group) } + let(:member) { create(:group_member) } + let(:user) { member.user } + + before do + solo_owned.group_members = [member] + DeleteUserService.new(current_user).execute(user, delete_solo_owned_groups: true) + end + + it 'deletes solo owned groups' do + expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'deletes the user' do + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb index 975b0470cb..14c5652128 100644 --- a/spec/workers/delete_user_worker_spec.rb +++ b/spec/workers/delete_user_worker_spec.rb @@ -1,58 +1,20 @@ require 'spec_helper' describe DeleteUserWorker do - describe "Deletes a user and all their personal projects" do - let!(:user) { create(:user) } - let!(:current_user) { create(:user) } - let!(:namespace) { create(:namespace, owner: user) } - let!(:project) { create(:project, namespace: namespace) } + let!(:user) { create(:user) } + let!(:current_user) { create(:user) } - context 'no force flag given' do - before do - DeleteUserWorker.new.perform(current_user.id, user.id) - end + it "calls the DeleteUserWorker with the params it was given" do + expect_any_instance_of(DeleteUserService).to receive(:execute). + with(user, {}) - it 'deletes all personal projects' do - expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound) - end + DeleteUserWorker.new.perform(current_user.id, user.id) + end - it 'deletes the user' do - expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) - end - end + it "uses symbolized keys" do + expect_any_instance_of(DeleteUserService).to receive(:execute). + with(user, test: "test") - context "solo owned groups present" do - let(:solo_owned) { create(:group) } - let(:member) { create(:group_member) } - let(:user) { member.user } - - before do - solo_owned.group_members = [member] - DeleteUserWorker.new.perform(current_user.id, user.id) - end - - it 'does not delete the user' do - expect(User.find(user.id)).to eq user - end - end - - context "deletions with force" do - let(:solo_owned) { create(:group) } - let(:member) { create(:group_member) } - let(:user) { member.user } - - before do - solo_owned.group_members = [member] - DeleteUserWorker.new.perform(current_user.id, user.id, "delete_solo_owned_groups" => true) - end - - it 'deletes solo owned groups' do - expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound) - end - - it 'deletes the user' do - expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) - end - end + DeleteUserWorker.new.perform(current_user.id, user.id, "test" => "test") end end From 7ae573c75ac51413c04249f77ed8ca4e144b7549 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 15 Mar 2016 23:22:05 +0100 Subject: [PATCH 035/153] Bump Git version requirement to 2.7.3 --- CHANGELOG | 3 +++ README.md | 2 +- doc/install/installation.md | 2 +- lib/tasks/gitlab/check.rake | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b120810ebd..6854e7cab8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,9 @@ v 8.6.0 (unreleased) - Move group activity to separate page - Continue parameters are checked to ensure redirection goes to the same instance +v 8.5.6 + - Bump Git version requirement to 2.7.3 + v 8.5.5 - Ensure removing a project removes associated Todo entries - Prevent a 500 error in Todos when author was removed diff --git a/README.md b/README.md index 3ec1d4a776..208427fcf8 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ GitLab is a Ruby on Rails application that runs on the following software: - Ubuntu/Debian/CentOS/RHEL - Ruby (MRI) 2.1 -- Git 1.7.10+ +- Git 2.7.3+ - Redis 2.8+ - MySQL or PostgreSQL diff --git a/doc/install/installation.md b/doc/install/installation.md index 4f01139726..362b27350c 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -76,7 +76,7 @@ Make sure you have the right version of Git installed # Install Git sudo apt-get install -y git-core - # Make sure Git is version 1.7.10 or higher, for example 1.7.12 or 2.0.0 + # Make sure Git is version 2.7.3 or higher git --version Is the system packaged Git too old? Remove it and compile from source. diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 581ab26db7..27ed57efe5 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -913,7 +913,7 @@ namespace :gitlab do end def check_git_version - required_version = Gitlab::VersionInfo.new(1, 7, 10) + required_version = Gitlab::VersionInfo.new(2, 7, 3) current_version = Gitlab::VersionInfo.parse(run(%W(#{Gitlab.config.git.bin_path} --version))) puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\"" From cd683714eb6240bb3d42dabb3f6dc470fc930fb4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 15 Mar 2016 23:24:27 +0100 Subject: [PATCH 036/153] Fix changelog --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 12f44a4e68..b83940d2f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,8 +40,10 @@ v 8.6.0 (unreleased) - Move group activity to separate page - Continue parameters are checked to ensure redirection goes to the same instance -v 8.5.6 +v 8.5.7 - Bump Git version requirement to 2.7.3 + +v 8.5.6 - Obtain a lease before querying LDAP v 8.5.5 From 24ea8d9df43e28b57dfa52b8302c754f76a05e0a Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 15 Mar 2016 22:43:11 +0000 Subject: [PATCH 037/153] Use relative link for installation.md --- doc/install/requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index a7dfa7ddf5..4c0f039f3e 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -22,7 +22,7 @@ For the installations options please see [the installation page on the GitLab we - FreeBSD On the above unsupported distributions is still possible to install GitLab yourself. -Please see the [installation from source guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) and the [installation guides](https://about.gitlab.com/installation/) for more information. +Please see the [installation from source guide](installation.md) and the [installation guides](https://about.gitlab.com/installation/) for more information. ### Non-Unix operating systems such as Windows From 7e03b40221388bd0567be6609f2ededa7f934179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Tue, 15 Mar 2016 19:51:27 -0500 Subject: [PATCH 038/153] Return an empty Array when there aren't lines to parse. --- lib/gitlab/diff/parser.rb | 2 ++ spec/lib/gitlab/diff/parser_spec.rb | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb index d0f6ba23ab..d0815fc7ee 100644 --- a/lib/gitlab/diff/parser.rb +++ b/lib/gitlab/diff/parser.rb @@ -4,6 +4,8 @@ module Gitlab include Enumerable def parse(lines) + return [] if lines.blank? + @lines = lines line_obj_index = 0 line_old = 1 diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index f576c39284..cdff063a9e 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -90,4 +90,9 @@ eos end end end + + context 'when lines is empty' do + it { expect(parser.parse([])).to eq([]) } + it { expect(parser.parse(nil)).to eq([]) } + end end From dce43b4bea88c753c6aeb43d2d283bef4d11eff1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 15 Mar 2016 17:17:05 +0000 Subject: [PATCH 039/153] Pre-selects values on issue and merge request dropdown Closes #14290 --- app/helpers/issuables_helper.rb | 28 +++++++++++++++++++++ app/views/shared/issuable/_filter.html.haml | 8 +++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 2dfeddf736..29b78ac910 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -20,6 +20,34 @@ module IssuablesHelper base_issuable_scope(issuable).where('iid < ?', issuable.iid).first end + def user_dropdown_label(user_id, default_label) + return "Unassigned" if user_id == "0" + + user = @project.team.users.find_by(id: user_id) + + if user + user.name + else + default_label + end + end + + def labels_dropdown_label(label_name) + if !label_name + "Label" + else + label_name + end + end + + def milestone_dropdown_label(milestone_name) + if !milestone_name + "Milestone" + else + milestone_name + end + end + private def sidebar_gutter_collapsed? diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 3eb0db276b..74ccaa53da 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -9,19 +9,19 @@ .filter-item.inline - if params[:author_id] = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag("Author", options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author", + = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author", placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id" } }) .filter-item.inline - if params[:assignee_id] = hidden_field_tag(:assignee_id, params[:assignee_id]) - = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee", + = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee", placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } }) .filter-item.inline.milestone-filter - if params[:milestone_title] = hidden_field_tag(:milestone_title, params[:milestone_title]) - = dropdown_tag("Milestone", options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", + = dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :js) if @project) } }) do - if @project %ul.dropdown-footer-list @@ -42,7 +42,7 @@ .dropdown %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :js) if @project)}} %span.dropdown-toggle-text - Label + = labels_dropdown_label(params[:label_name]) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable .dropdown-page-one From 2e07ee7f01e9bd525c98ae3f84025dd8699fdf8a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 15 Mar 2016 18:28:25 +0000 Subject: [PATCH 040/153] Fixed failing tests --- app/helpers/issuables_helper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 29b78ac910..b254c62da2 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -23,7 +23,8 @@ module IssuablesHelper def user_dropdown_label(user_id, default_label) return "Unassigned" if user_id == "0" - user = @project.team.users.find_by(id: user_id) + user = @project.team.users.find_by(id: user_id) if @project + user = User.find_by_id(user_id) if !@project if user user.name From 438d614b2780e280256af81f1e3f2f1847b3405e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 08:48:27 +0000 Subject: [PATCH 041/153] Removed label/milestone helper methods --- app/helpers/issuables_helper.rb | 26 +++++++-------------- app/views/shared/issuable/_filter.html.haml | 4 ++-- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index b254c62da2..23f389b8c5 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -23,8 +23,14 @@ module IssuablesHelper def user_dropdown_label(user_id, default_label) return "Unassigned" if user_id == "0" - user = @project.team.users.find_by(id: user_id) if @project - user = User.find_by_id(user_id) if !@project + if @project + member = @project.team.find_member(user_id) + if member + user = member.user + end + else + user = User.find(user_id) + end if user user.name @@ -33,22 +39,6 @@ module IssuablesHelper end end - def labels_dropdown_label(label_name) - if !label_name - "Label" - else - label_name - end - end - - def milestone_dropdown_label(milestone_name) - if !milestone_name - "Milestone" - else - milestone_name - end - end - private def sidebar_gutter_collapsed? diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 74ccaa53da..dfdc84ba4c 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -21,7 +21,7 @@ .filter-item.inline.milestone-filter - if params[:milestone_title] = hidden_field_tag(:milestone_title, params[:milestone_title]) - = dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", + = dropdown_tag(h(params[:milestone_name] || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :js) if @project) } }) do - if @project %ul.dropdown-footer-list @@ -42,7 +42,7 @@ .dropdown %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :js) if @project)}} %span.dropdown-toggle-text - = labels_dropdown_label(params[:label_name]) + = h(params[:label_name] || "Label") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable .dropdown-page-one From 2941a76fe0fa5305bd6e31e6c47477732e3e7cec Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 08:51:09 +0000 Subject: [PATCH 042/153] Added skip_js comment [ci skip] --- app/helpers/application_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f07c79ec61..ffddd0400f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -168,6 +168,7 @@ module ApplicationHelper # time - Time object # placement - Tooltip placement String (default: "top") # html_class - Custom class for `time` element (default: "time_ago") + # skip_js - When true, exclude the `script` tag (default: false) # # By default also includes a `script` element with Javascript necessary to # initialize the `timeago` jQuery extension. If this method is called many From d94dc565ba479e969ad548991b411e058330d8f9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 11:44:53 +0000 Subject: [PATCH 043/153] Fixed alignment of download dropdown With the new dropdown CSS - some dropdowns might look a bit odd --- app/assets/stylesheets/framework/dropdowns.scss | 6 ++++++ app/views/projects/repositories/_download_archive.html.haml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 309da34da0..d71ca9842a 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -9,6 +9,12 @@ border-left: $caret-width-base solid transparent; } +.btn-group { + .caret { + margin-left: 0; + } +} + .dropdown { position: relative; } diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index b9486a9b49..2465831906 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -10,7 +10,7 @@ %span.caret %span.sr-only Select Archive Format - %ul.col-xs-10.dropdown-menu{ role: 'menu' } + %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } %li = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do %i.fa.fa-download From d6caadd1d533eaacde8e7e315546e7c5425d60e6 Mon Sep 17 00:00:00 2001 From: Daniel Carraro Date: Mon, 29 Feb 2016 16:37:27 +1100 Subject: [PATCH 044/153] Added omniauth-auth0 Gem + Applicable Documentation. --- CHANGELOG | 1 + Gemfile | 1 + Gemfile.lock | 3 ++ config/gitlab.yml.example | 6 +++ doc/integration/auth0.md | 89 +++++++++++++++++++++++++++++++++++++ doc/integration/omniauth.md | 1 + 6 files changed, 101 insertions(+) create mode 100644 doc/integration/auth0.md diff --git a/CHANGELOG b/CHANGELOG index fcf659c07f..08018abd20 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.6.0 (unreleased) - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - Memoize @group in Admin::GroupsController (Yatish Mehta) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) + - Added omniauth-auth0 Gem (Daniel Carraro) - Strip leading and trailing spaces in URL validator (evuez) - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez) - Return empty array instead of 404 when commit has no statuses in commit status API diff --git a/Gemfile b/Gemfile index a849d7493a..a3fb6779e9 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ gem 'devise', '~> 3.5.4' gem 'devise-async', '~> 0.9.0' gem 'doorkeeper', '~> 2.2.0' gem 'omniauth', '~> 1.3.1' +gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-cas3', '~> 1.1.2' diff --git a/Gemfile.lock b/Gemfile.lock index 669bfcf4d6..7b0dd83da5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -498,6 +498,8 @@ GEM omniauth (1.3.1) hashie (>= 1.2, < 4) rack (>= 1.0, < 3) + omniauth-auth0 (1.4.1) + omniauth-oauth2 (~> 1.1) omniauth-azure-oauth2 (0.0.6) jwt (~> 1.0) omniauth (~> 1.0) @@ -973,6 +975,7 @@ DEPENDENCIES oauth2 (~> 1.0.0) octokit (~> 3.8.0) omniauth (~> 1.3.1) + omniauth-auth0 (~> 1.4.1) omniauth-azure-oauth2 (~> 0.0.6) omniauth-bitbucket (~> 0.0.2) omniauth-cas3 (~> 1.1.2) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 05f127d622..500b745f55 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -357,6 +357,12 @@ production: &base # crowd_server_url: 'CROWD SERVER URL', # application_name: 'YOUR_APP_NAME', # application_password: 'YOUR_APP_PASSWORD' } } + # + # - { name: 'auth0', + # args: { + # client_id: 'YOUR_AUTH0_CLIENT_ID', + # client_secret: 'YOUR_AUTH0_CLIENT_SECRET', + # namespace: 'YOUR_AUTH0_DOMAIN' } } # SSO maximum session duration in seconds. Defaults to CAS default of 8 hours. # cas3: diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md new file mode 100644 index 0000000000..e5247082a8 --- /dev/null +++ b/doc/integration/auth0.md @@ -0,0 +1,89 @@ +# Auth0 OmniAuth Provider + +To enable the Auth0 OmniAuth provider, you must create an Auth0 account, and an +application. + +1. Sign in to the [Auth0 Console](https://manage.auth0.com). If you need to +create an account, you can do so at the same link. + +1. Select "New App/API". + +1. Provide the Application Name ('GitLab' works fine). + +1. Once created, you should see the Quick Start options. Disregard them and +select 'Settings' above the Quick Start options. + +1. At the top of the Settings screen, you should see your Domain, Client ID and +Client Secret. Take note of these as you'll need to put them in the +configuration file. For example: + - Domain: `test1234.auth0.com` + - Client ID: `t6X8L2465bNePWLOvt9yi41i` + - Client Secret: `KbveM3nqfjwCbrhaUy_gDu2dss8TIlHIdzlyf33pB7dEK5u_NyQdp65O_o02hXs2` + +1. Fill in the Allowed Callback URLs: + - http://`YOUR_GITLAB_URL`/users/auth/auth0/callback (or) + - https://`YOUR_GITLAB_URL`/users/auth/auth0/callback + +1. Fill in the Allowed Origins (CORS): + - http://`YOUR_GITLAB_URL` (or) + - https://`YOUR_GITLAB_URL` + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For installations from source: + + ```sh + cd /home/git/gitlab + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) +for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "auth0", + "args" => { client_id: 'YOUR_AUTH0_CLIENT_ID'', + client_secret: 'YOUR_AUTH0_CLIENT_SECRET', + namespace: 'YOUR_AUTH0_DOMAIN' + } + } + ] + ``` + + For installations from source: + + ```yaml + - { name: 'auth0', + args: { + client_id: 'YOUR_AUTH0_CLIENT_ID', + client_secret: 'YOUR_AUTH0_CLIENT_SECRET', + namespace: 'YOUR_AUTH0_DOMAIN' + } + } + ``` + +1. Change `YOUR_AUTH0_CLIENT_ID` to the client ID from the Auth0 Console page +from step 5. + +1. Change `YOUR_AUTH0_CLIENT_SECRET` to the client secret from the Auth0 Console +page from step 5. + +1. Save the file and [reconfigure GitLab](../administration/restart_gitlab.md) +for the changes to take effect. + +On the sign in page there should now be an Auth0 icon below the regular sign in +form. Click the icon to begin the authentication process. Auth0 will ask the +user to sign in and authorize the GitLab application. If everything goes well +the user will be returned to GitLab and will be signed in. diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index ba47cb1626..25f3598830 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -28,6 +28,7 @@ contains some settings that are common for all providers. - [SAML](saml.md) - [Crowd](crowd.md) - [Azure](azure.md) +- [Auth0](auth0.md) ## Initial OmniAuth Configuration From af4d5458a9f2f45f781f5baab05eaa9a8113328d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 16 Mar 2016 13:38:18 +0100 Subject: [PATCH 045/153] Added an index on ci_runners.token On PostgreSQL the index is created concurrently. Fixes gitlab-org/gitlab-ce#14325 --- db/migrate/20160316123110_ci_runners_token_index.rb | 13 +++++++++++++ db/schema.rb | 9 +++++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20160316123110_ci_runners_token_index.rb diff --git a/db/migrate/20160316123110_ci_runners_token_index.rb b/db/migrate/20160316123110_ci_runners_token_index.rb new file mode 100644 index 0000000000..67bf5b4f97 --- /dev/null +++ b/db/migrate/20160316123110_ci_runners_token_index.rb @@ -0,0 +1,13 @@ +class CiRunnersTokenIndex < ActiveRecord::Migration + disable_ddl_transaction! + + def change + args = [:ci_runners, :token] + + if Gitlab::Database.postgresql? + args << { algorithm: :concurrently } + end + + add_index(*args) + end +end diff --git a/db/schema.rb b/db/schema.rb index 5027d2ba32..2c27b22886 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160314143402) do +ActiveRecord::Schema.define(version: 20160316123110) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -260,6 +260,7 @@ ActiveRecord::Schema.define(version: 20160314143402) do end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| @@ -775,9 +776,9 @@ ActiveRecord::Schema.define(version: 20160314143402) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true From 25600f530f9eac4a7c5ab03dd6b0a51e84922eb7 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 16 Mar 2016 13:40:27 +0100 Subject: [PATCH 046/153] Update Git source installation steps to 2.7.3 --- doc/install/installation.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 2dd5e495ed..1894075b72 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -89,8 +89,9 @@ Is the system packaged Git too old? Remove it and compile from source. # Download and compile from source cd /tmp - curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.4.3.tar.gz | tar xz - cd git-2.4.3/ + curl -O --progress https://www.kernel.org/pub/software/scm/git/git-2.7.3.tar.gz + echo '30d067499b61caddedaf1a407b4947244f14d10842d100f7c7c6ea1c288280cd git-2.7.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.7.3.tar.gz + cd git-2.7.3/ ./configure make prefix=/usr/local all From 96ac3021276babe14d7ec362fdd71f8d69238ffa Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 16 Mar 2016 13:41:07 +0100 Subject: [PATCH 047/153] Fix golang shasum invocation --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 1894075b72..aa989417c4 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -144,7 +144,7 @@ use 64-bit Linux. You can find downloads for other platforms at the [Go download page](https://golang.org/dl). curl -O --progress https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz - echo '43afe0c5017e502630b1aea4d44b8a7f059bf60d7f29dfd58db454d4e4e0ae53 go1.5.3.linux-amd64.tar.gz' | shasum -c - && \ + echo '43afe0c5017e502630b1aea4d44b8a7f059bf60d7f29dfd58db454d4e4e0ae53 go1.5.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ sudo tar -C /usr/local -xzf go1.5.3.linux-amd64.tar.gz sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ rm go1.5.3.linux-amd64.tar.gz From 7cbf8fd0d089264f863dc01217514d7e2c40e056 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 9 Mar 2016 07:51:52 +0100 Subject: [PATCH 048/153] Add tests for MergeRequest#source_sha --- spec/models/merge_request_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 8bf68013fd..aceb2406eb 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -86,6 +86,24 @@ describe MergeRequest, models: true do end end + describe '#source_sha' do + let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) } + + context 'with diffs' do + subject { create(:merge_request, :with_diffs) } + it 'returns the sha of the source branch last commit' do + expect(subject.source_sha).to eq(last_branch_commit.sha) + end + end + + context 'when the merge request is being created' do + subject { build(:merge_request, source_branch: nil, compare_commits: []) } + it 'returns nil' do + expect(subject.source_sha).to be_nil + end + end + end + describe '#to_reference' do it 'returns a String reference to the object' do expect(subject.to_reference).to eq "!#{subject.iid}" From 18295585d9d2a7177f52fc451db7b0d542cc49b5 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 9 Mar 2016 08:19:54 +0100 Subject: [PATCH 049/153] Fix MergeRequest#source_sha when there is no diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `MergeRequest#source_sha` is expected to return the sha of the source branch last commit. But when a open Merge Request has no diff (e.g. all commits have already been merged to the target branch), `merge_request.source_sha` incorrectly returns `nil`. This was un-noticed before – but now that !2217 has been merged, it makes `Gitlab::Git::Commit.between` raise an "Unexpected nil argument" exception. This fixes the crash, by making sure that `source_sha` returns a correct result even when there is no diff available. --- CHANGELOG | 1 + app/models/merge_request.rb | 6 +++++- spec/factories/merge_requests.rb | 5 +++++ spec/models/merge_request_spec.rb | 7 +++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fcf659c07f..ccc356a961 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -33,6 +33,7 @@ v 8.6.0 (unreleased) - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio) - Don't show Issues/MRs from archived projects in Groups view - Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs + - Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie) - Increase the notes polling timeout over time (Roberto Dip) - Add shortcut to toggle markdown preview (Florent Baldino) - Show labels in dashboard and group milestone views diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 188325045e..30a7bd47be 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -520,7 +520,11 @@ class MergeRequest < ActiveRecord::Base end def source_sha - last_commit.try(:sha) + last_commit.try(:sha) || source_tip.try(:sha) + end + + def source_tip + source_branch && source_project.repository.commit(source_branch) end def fetch_ref diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index a9df5fa1d3..e281e2f227 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -51,6 +51,11 @@ FactoryGirl.define do trait :with_diffs do end + trait :without_diffs do + source_branch "improve/awesome" + target_branch "master" + end + trait :conflict do source_branch "feature_conflict" target_branch "feature" diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index aceb2406eb..619c01b552 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -96,6 +96,13 @@ describe MergeRequest, models: true do end end + context 'without diffs' do + subject { create(:merge_request, :without_diffs) } + it 'returns the sha of the source branch last commit' do + expect(subject.source_sha).to eq(last_branch_commit.sha) + end + end + context 'when the merge request is being created' do subject { build(:merge_request, source_branch: nil, compare_commits: []) } it 'returns nil' do From 315d87595404a8bde78566ec51e23b167f041048 Mon Sep 17 00:00:00 2001 From: Mark Pundsack Date: Wed, 16 Mar 2016 18:04:42 +0000 Subject: [PATCH 050/153] Fix typo in README.md. --- doc/ci/deployment/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/deployment/README.md b/doc/ci/deployment/README.md index ffd841ca9e..7d91ce6710 100644 --- a/doc/ci/deployment/README.md +++ b/doc/ci/deployment/README.md @@ -89,7 +89,7 @@ We also use two secure variables: In GitLab CI 7.12 a new feature was introduced: Secure Variables. Secure Variables can added by going to `Project > Variables > Add Variable`. **This feature requires `gitlab-runner` with version equal or greater than 0.4.0.** -The variables that are defined in the project settings are send along with the build script to the runner. +The variables that are defined in the project settings are sent along with the build script to the runner. The secure variables are stored out of the repository. Never store secrets in your projects' .gitlab-ci.yml. It is also important that secret's value is hidden in the build log. From 6f9bda6b2d08e264414a24468ae9870e2ecb6572 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 19:29:53 +0000 Subject: [PATCH 051/153] Fixes issue with dropdown caret being incorrect color --- app/assets/stylesheets/framework/buttons.scss | 1 - app/assets/stylesheets/framework/dropdowns.scss | 2 +- app/assets/stylesheets/framework/variables.scss | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 76611efb78..10fe42239b 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -139,7 +139,6 @@ .caret { margin-left: 5px; - color: $gray-darkest; } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 968f9cb76c..3197ea8446 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -4,7 +4,7 @@ height: 0; margin-left: 2px; vertical-align: middle; - border-top: $caret-width-base dashed $dropdown-caret-color; + border-top: $caret-width-base dashed; border-right: $caret-width-base solid transparent; border-left: $caret-width-base solid transparent; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3447c558e8..5e3546bc6f 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -143,7 +143,6 @@ $dropdown-border-color: rgba(#000, .1); $dropdown-shadow-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1); $dropdown-header-color: #959494; -$dropdown-caret-color: #54565b; $dropdown-title-btn-color: #bfbfbf; $dropdown-input-color: #c7c7c7; $dropdown-input-focus-border: rgb(58, 171, 240); From 65565bb304238a2e7d260faccd1a033d6da343e9 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Wed, 16 Mar 2016 21:38:38 +0100 Subject: [PATCH 052/153] fix horizontal overflow that occurs in the code tag --- app/assets/stylesheets/pages/notes.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 1f631ab03e..f7eeadcb5b 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -108,6 +108,12 @@ ul.notes { word-wrap: break-word; @include md-typography; + pre { + code { + white-space: pre-wrap; + } + } + // Reset ul style types since we're nested inside a ul already & > ul { list-style-type: disc; From 49f2a4e3a6ebe2deb02abe48145e5ff33ccb7971 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Wed, 16 Mar 2016 23:30:01 +0100 Subject: [PATCH 053/153] fix bug causing dropdown to get cut off in project page --- app/assets/stylesheets/pages/projects.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index a29b735eb0..aca4d8d1b7 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -33,6 +33,13 @@ .project-settings-dropdown { margin-left: 10px; display: inline-block; + + .dropdown-menu { + left: auto; + width: auto; + right: 0px; + max-width: 240px; + } } } From 6c06b65d1e905775c2cd31e64c4bf2aa3f14ef5b Mon Sep 17 00:00:00 2001 From: Anatoly Borodin Date: Thu, 17 Mar 2016 00:35:53 +0100 Subject: [PATCH 054/153] Use relative links in `doc`, fix broken generated HTML links Links like (doc/web_hooks/web_hooks.md) work in the GitLab source code web interface, but the HTML generator produces broken links in the `doc` subdirectories: http://doc.gitlab.com/ce/hooks/doc/web_hooks/web_hooks.html instead of the right one http://doc.gitlab.com/ce/web_hooks/web_hooks.html in http://doc.gitlab.com/ce/hooks/custom_hooks.html. Fixes #14338 [ci skip] Signed-off-by: Anatoly Borodin --- doc/hooks/custom_hooks.md | 2 +- doc/release/security.md | 2 +- doc/security/two_factor_authentication.md | 2 +- doc/workflow/importing/import_projects_from_bitbucket.md | 2 +- doc/workflow/protected_branches.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index 15051dd76f..dcdf49d337 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -2,7 +2,7 @@ **Note: Custom git hooks must be configured on the filesystem of the GitLab server. Only GitLab server administrators will be able to complete these tasks. -Please explore [webhooks](doc/web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).** +Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).** Git natively supports hooks that are executed on different actions. Examples of server-side git hooks include pre-receive, post-receive, and update. diff --git a/doc/release/security.md b/doc/release/security.md index b1a62b333e..118c016ba4 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -15,7 +15,7 @@ Please report suspected security vulnerabilities in private to Date: Wed, 9 Mar 2016 13:50:34 +0900 Subject: [PATCH 055/153] Fix wiki search results point to raw source --- CHANGELOG | 1 + app/models/repository.rb | 4 ++++ app/views/search/results/_wiki_blob.html.haml | 4 ++-- features/steps/search.rb | 2 +- spec/models/repository_spec.rb | 18 +++++++++++++++++- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fcf659c07f..8a2359e396 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.6.0 (unreleased) GitLab will ask the user to create a new one upon first visit. - Fix issue when pushing to projects ending in .wiki - Add support for wiki with UTF-8 page names (Hiroyuki Sato) + - Fix wiki search results point to raw source (Hiroyuki Sato) - Don't load all of GitLab in mail_room - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - Memoize @group in Admin::GroupsController (Yatish Mehta) diff --git a/app/models/repository.rb b/app/models/repository.rb index 6441cd87e8..e555e97689 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -758,12 +758,15 @@ class Repository def parse_search_result(result) ref = nil filename = nil + basename = nil startline = 0 result.each_line.each_with_index do |line, index| if line =~ /^.*:.*:\d+:/ ref, filename, startline = line.split(':') startline = startline.to_i - index + extname = File.extname(filename) + basename = filename.sub(/#{extname}$/, '') break end end @@ -776,6 +779,7 @@ class Repository OpenStruct.new( filename: filename, + basename: basename, ref: ref, startline: startline, data: data diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index f5859481d4..235106c4f7 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -2,9 +2,9 @@ .blob-result .file-holder .file-title - = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.filename) do + = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.basename) do %i.fa.fa-file %strong - = wiki_blob.filename + = wiki_blob.basename .file-content.code.term = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline diff --git a/features/steps/search.rb b/features/steps/search.rb index 48ea3fa387..0ad837ebe1 100644 --- a/features/steps/search.rb +++ b/features/steps/search.rb @@ -100,7 +100,7 @@ class Spinach::Features::Search < Spinach::FeatureSteps step 'I should see "test_wiki" link in the search results' do page.within('.results') do - find(:css, '.search-results').should have_link 'test_wiki.md' + expect(find(:css, '.search-results')).to have_link 'test_wiki' end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 34866be339..fc2ab2d993 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -101,13 +101,29 @@ describe Repository, models: true do end describe 'parsing result' do - subject { repository.parse_search_result(results.first) } + subject { repository.parse_search_result(search_result) } + let(:search_result) { results.first } it { is_expected.to be_an OpenStruct } it { expect(subject.filename).to eq('CHANGELOG') } + it { expect(subject.basename).to eq('CHANGELOG') } it { expect(subject.ref).to eq('master') } it { expect(subject.startline).to eq(186) } it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") } + + context "when filename has extension" do + let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" } + + it { expect(subject.filename).to eq('CONTRIBUTE.md') } + it { expect(subject.basename).to eq('CONTRIBUTE') } + end + + context "when file under directory" do + let(:search_result) { "master:a/b/c.md:5:a b c\n" } + + it { expect(subject.filename).to eq('a/b/c.md') } + it { expect(subject.basename).to eq('a/b/c') } + end end end From e106597e31490a0dcfa9ff0fe5f88b13c19fd766 Mon Sep 17 00:00:00 2001 From: connorshea Date: Wed, 16 Mar 2016 17:29:47 -0600 Subject: [PATCH 056/153] Follow the CSS Style Guide rules for unitless zero values. Updated Emoji Rake task to conform to style guide. Discussed in #14299. [ci skip] --- app/assets/stylesheets/framework/blocks.scss | 2 +- app/assets/stylesheets/framework/buttons.scss | 4 +- app/assets/stylesheets/framework/files.scss | 2 +- app/assets/stylesheets/framework/header.scss | 2 +- .../stylesheets/framework/highlight.scss | 4 +- app/assets/stylesheets/framework/mixins.scss | 6 +- app/assets/stylesheets/framework/mobile.scss | 2 +- app/assets/stylesheets/framework/nav.scss | 4 +- app/assets/stylesheets/framework/selects.scss | 2 +- app/assets/stylesheets/framework/sidebar.scss | 10 +- .../stylesheets/framework/tw_bootstrap.scss | 2 +- .../stylesheets/framework/typography.scss | 4 +- app/assets/stylesheets/framework/zen.scss | 2 +- app/assets/stylesheets/pages/commit.scss | 2 +- app/assets/stylesheets/pages/commits.scss | 2 +- app/assets/stylesheets/pages/diff.scss | 36 ++-- app/assets/stylesheets/pages/emojis.scss | 164 +++++++++--------- app/assets/stylesheets/pages/events.scss | 2 +- app/assets/stylesheets/pages/import.scss | 2 +- app/assets/stylesheets/pages/login.scss | 6 +- app/assets/stylesheets/pages/notes.scss | 4 +- app/assets/stylesheets/pages/profile.scss | 2 +- app/assets/stylesheets/pages/projects.scss | 6 +- app/assets/stylesheets/pages/sherlock.scss | 4 +- app/assets/stylesheets/pages/todos.scss | 2 +- lib/tasks/gemojione.rake | 1 + 26 files changed, 140 insertions(+), 139 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index d20b77ffae..90c3ce0e84 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -23,7 +23,7 @@ margin-bottom: -$gl-padding; background-color: $background-color; padding: $gl-padding; - margin-bottom: 0px; + margin-bottom: 0; border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; color: $gl-gray; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 10fe42239b..fa115a4bf5 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -127,7 +127,7 @@ margin-right: 7px; float: left; &:last-child { - margin-right: 0px; + margin-right: 0; } &.btn-xs { margin-right: 3px; @@ -169,7 +169,7 @@ margin-right: 7px; float: left; &:last-child { - margin-right: 0px; + margin-right: 0; } } } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 116cc0df81..646e261083 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -30,7 +30,7 @@ right: 15px; .btn { - padding: 0px 10px; + padding: 0 10px; font-size: 13px; line-height: 28px; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 5f70cd399f..71a7ecab8e 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -162,7 +162,7 @@ header { font-size: 18px; .navbar-nav { - margin: 0px; + margin: 0; float: none !important; .visible-xs, .visable-sm { diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss index 12e2f00fe8..7cf4d4fba4 100644 --- a/app/assets/stylesheets/framework/highlight.scss +++ b/app/assets/stylesheets/framework/highlight.scss @@ -1,8 +1,8 @@ .file-content.code { border: none; box-shadow: none; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; table-layout: fixed; pre { diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 4b59c758f1..377bfa174b 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -67,8 +67,8 @@ * Base mixin for lists in GitLab */ @mixin basic-list { - margin: 5px 0px; - padding: 0px; + margin: 5px 0; + padding: 0; list-style: none; > li { @@ -77,7 +77,7 @@ padding: 10px 0; border-bottom: 1px solid #eee; display: block; - margin: 0px; + margin: 0; &:last-child { border-bottom: none; diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index ec10a70cf0..5ea4f9a49d 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -128,7 +128,7 @@ .show-aside { display: none; position: fixed; - right: 0px; + right: 0; top: 30%; padding: 5px 15px; background: #eee; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index b53872bf67..5f4ce87b08 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -59,7 +59,7 @@ .nav-links { display: inline-block; width: 50%; - margin-bottom: 0px; + margin-bottom: 0; border-bottom: none; /* Small devices (phones, tablets, 768px and lower) */ @@ -74,7 +74,7 @@ float: right; text-align: right; padding: 11px 0; - margin-bottom: 0px; + margin-bottom: 0; > .dropdown { margin-right: $gl-padding-top; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index cbcfef8a69..b3371229d5 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -151,7 +151,7 @@ padding: 2px 25px 2px 5px; background: #fff image-url('select2.png'); background-repeat: no-repeat; - background-position: right 0px bottom 6px; + background-position: right 0 bottom 6px; border: 1px solid $input-border; @include border-radius($border-radius-default); @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s); diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 29570f2ce3..be05db58c4 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -16,7 +16,7 @@ .gitlab-text-container-link { z-index: 1; position: absolute; - left: 0px; + left: 0; } #logo { @@ -143,7 +143,7 @@ overflow: hidden; &.navbar-collapse { - padding: 0px !important; + padding: 0 !important; } li { @@ -182,7 +182,7 @@ .count { float: right; background: #eee; - padding: 0px 8px; + padding: 0 8px; @include border-radius(6px); } @@ -194,8 +194,8 @@ } .sidebar-subnav { - margin-left: 0px; - padding-left: 0px; + margin-left: 0; + padding-left: 0; li { list-style: none; diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 9946dd3e9b..dd42db1840 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -138,7 +138,7 @@ } .btn-clipboard { - min-width: 0px; + min-width: 0; } } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 6ff8852c2f..949295a1d0 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -33,7 +33,7 @@ border-color: #ccc #ccc #bbb; border-image: none; border-radius: 3px; - box-shadow: 0px -1px 0px #bbb inset; + box-shadow: 0 -1px 0 #bbb inset; } h1 { @@ -187,7 +187,7 @@ body { } .page-title-empty { - margin-top: 0px; + margin-top: 0; line-height: 1.3; font-size: 1.25em; font-weight: 600; diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss index c3f27333fa..02e24ec7c4 100644 --- a/app/assets/stylesheets/framework/zen.scss +++ b/app/assets/stylesheets/framework/zen.scss @@ -2,7 +2,7 @@ a.js-zen-enter { color: $gl-gray; position: absolute; - top: 0px; + top: 0; right: 4px; line-height: 56px; } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index c0cc30d33a..971656feb4 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -55,7 +55,7 @@ padding: 10px 0; li { - padding: 3px 0px; + padding: 3px 0; line-height: 20px; } } diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 2062cd2ebc..d57be1b2da 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -152,7 +152,7 @@ li.commit { .count { padding-top: 6px; - padding-bottom: 0px; + padding-bottom: 0; font-size: 12px; color: #333; display: block; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 4739aa0502..db06b8288c 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -57,8 +57,8 @@ font-family: $monospace_font; border: none; border-collapse: separate; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; .line_holder td { line-height: $code_line_height; font-size: $code_font_size; @@ -76,10 +76,10 @@ } .old_line, .new_line { - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; border: none; - padding: 0px 5px; + padding: 0 5px; border-right: 1px solid; text-align: right; min-width: 35px; @@ -97,8 +97,8 @@ } .line_content { display: block; - margin: 0px; - padding: 0px 0.5em; + margin: 0; + padding: 0 0.5em; border: none; &.parallel { display: table-cell; @@ -183,7 +183,7 @@ height: 14px; width: 15px; position: absolute; - top: 0px; + top: 0; background: image-url('swipemode_sprites.gif') 0 3px no-repeat; } .bottom-handle { @@ -191,7 +191,7 @@ height: 14px; width: 15px; position: absolute; - bottom: 0px; + bottom: 0; background: image-url('swipemode_sprites.gif') 0 -11px no-repeat; } } @@ -206,8 +206,8 @@ .frame.added, .frame.deleted { position: absolute; display: block; - top: 0px; - left: 0px; + top: 0; + left: 0; } .controls { display: block; @@ -215,7 +215,7 @@ width: 300px; z-index: 100; position: absolute; - bottom: 0px; + bottom: 0; left: 50%; margin-left: -150px; @@ -231,11 +231,11 @@ .dragger { display: block; position: absolute; - left: 0px; - top: 0px; + left: 0; + top: 0; height: 14px; width: 14px; - background: image-url('onion_skin_sprites.gif') 0px -34px repeat-x; + background: image-url('onion_skin_sprites.gif') 0 -34px repeat-x; cursor: pointer; } @@ -243,17 +243,17 @@ display: block; position: absolute; top: 2px; - right: 0px; + right: 0; height: 10px; width: 10px; - background: image-url('onion_skin_sprites.gif') -2px 0px no-repeat; + background: image-url('onion_skin_sprites.gif') -2px 0 no-repeat; } .opaque { display: block; position: absolute; top: 2px; - left: 0px; + left: 0; height: 10px; width: 10px; background: image-url('onion_skin_sprites.gif') -2px -10px no-repeat; diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss index 6c721b514f..b731abc745 100644 --- a/app/assets/stylesheets/pages/emojis.scss +++ b/app/assets/stylesheets/pages/emojis.scss @@ -1,60 +1,60 @@ -.emoji-0023-20E3 { background-position: 0px 0px; } -.emoji-002A-20E3 { background-position: -20px 0px; } -.emoji-0030-20E3 { background-position: 0px -20px; } +.emoji-0023-20E3 { background-position: 0 0; } +.emoji-002A-20E3 { background-position: -20px 0; } +.emoji-0030-20E3 { background-position: 0 -20px; } .emoji-0031-20E3 { background-position: -20px -20px; } -.emoji-0032-20E3 { background-position: -40px 0px; } +.emoji-0032-20E3 { background-position: -40px 0; } .emoji-0033-20E3 { background-position: -40px -20px; } -.emoji-0034-20E3 { background-position: 0px -40px; } +.emoji-0034-20E3 { background-position: 0 -40px; } .emoji-0035-20E3 { background-position: -20px -40px; } .emoji-0036-20E3 { background-position: -40px -40px; } -.emoji-0037-20E3 { background-position: -60px 0px; } +.emoji-0037-20E3 { background-position: -60px 0; } .emoji-0038-20E3 { background-position: -60px -20px; } .emoji-0039-20E3 { background-position: -60px -40px; } -.emoji-00A9 { background-position: 0px -60px; } +.emoji-00A9 { background-position: 0 -60px; } .emoji-00AE { background-position: -20px -60px; } .emoji-1F004 { background-position: -40px -60px; } .emoji-1F0CF { background-position: -60px -60px; } -.emoji-1F170 { background-position: -80px 0px; } +.emoji-1F170 { background-position: -80px 0; } .emoji-1F171 { background-position: -80px -20px; } .emoji-1F17E { background-position: -80px -40px; } .emoji-1F17F { background-position: -80px -60px; } -.emoji-1F18E { background-position: 0px -80px; } +.emoji-1F18E { background-position: 0 -80px; } .emoji-1F191 { background-position: -20px -80px; } .emoji-1F192 { background-position: -40px -80px; } .emoji-1F193 { background-position: -60px -80px; } .emoji-1F194 { background-position: -80px -80px; } -.emoji-1F195 { background-position: -100px 0px; } +.emoji-1F195 { background-position: -100px 0; } .emoji-1F196 { background-position: -100px -20px; } .emoji-1F197 { background-position: -100px -40px; } .emoji-1F198 { background-position: -100px -60px; } .emoji-1F199 { background-position: -100px -80px; } -.emoji-1F19A { background-position: 0px -100px; } +.emoji-1F19A { background-position: 0 -100px; } .emoji-1F1E6-1F1E8 { background-position: -20px -100px; } .emoji-1F1E6-1F1E9 { background-position: -40px -100px; } .emoji-1F1E6-1F1EA { background-position: -60px -100px; } .emoji-1F1E6-1F1EB { background-position: -80px -100px; } .emoji-1F1E6-1F1EC { background-position: -100px -100px; } -.emoji-1F1E6-1F1EE { background-position: -120px 0px; } +.emoji-1F1E6-1F1EE { background-position: -120px 0; } .emoji-1F1E6-1F1F1 { background-position: -120px -20px; } .emoji-1F1E6-1F1F2 { background-position: -120px -40px; } .emoji-1F1E6-1F1F4 { background-position: -120px -60px; } .emoji-1F1E6-1F1F6 { background-position: -120px -80px; } .emoji-1F1E6-1F1F7 { background-position: -120px -100px; } -.emoji-1F1E6-1F1F8 { background-position: 0px -120px; } +.emoji-1F1E6-1F1F8 { background-position: 0 -120px; } .emoji-1F1E6-1F1F9 { background-position: -20px -120px; } .emoji-1F1E6-1F1FA { background-position: -40px -120px; } .emoji-1F1E6-1F1FC { background-position: -60px -120px; } .emoji-1F1E6-1F1FD { background-position: -80px -120px; } .emoji-1F1E6-1F1FF { background-position: -100px -120px; } .emoji-1F1E7-1F1E6 { background-position: -120px -120px; } -.emoji-1F1E7-1F1E7 { background-position: -140px 0px; } +.emoji-1F1E7-1F1E7 { background-position: -140px 0; } .emoji-1F1E7-1F1E9 { background-position: -140px -20px; } .emoji-1F1E7-1F1EA { background-position: -140px -40px; } .emoji-1F1E7-1F1EB { background-position: -140px -60px; } .emoji-1F1E7-1F1EC { background-position: -140px -80px; } .emoji-1F1E7-1F1ED { background-position: -140px -100px; } .emoji-1F1E7-1F1EE { background-position: -140px -120px; } -.emoji-1F1E7-1F1EF { background-position: 0px -140px; } +.emoji-1F1E7-1F1EF { background-position: 0 -140px; } .emoji-1F1E7-1F1F1 { background-position: -20px -140px; } .emoji-1F1E7-1F1F2 { background-position: -40px -140px; } .emoji-1F1E7-1F1F3 { background-position: -60px -140px; } @@ -62,7 +62,7 @@ .emoji-1F1E7-1F1F6 { background-position: -100px -140px; } .emoji-1F1E7-1F1F7 { background-position: -120px -140px; } .emoji-1F1E7-1F1F8 { background-position: -140px -140px; } -.emoji-1F1E7-1F1F9 { background-position: -160px 0px; } +.emoji-1F1E7-1F1F9 { background-position: -160px 0; } .emoji-1F1E7-1F1FB { background-position: -160px -20px; } .emoji-1F1E7-1F1FC { background-position: -160px -40px; } .emoji-1F1E7-1F1FE { background-position: -160px -60px; } @@ -70,7 +70,7 @@ .emoji-1F1E8-1F1E6 { background-position: -160px -100px; } .emoji-1F1E8-1F1E8 { background-position: -160px -120px; } .emoji-1F1E8-1F1E9 { background-position: -160px -140px; } -.emoji-1F1E8-1F1EB { background-position: 0px -160px; } +.emoji-1F1E8-1F1EB { background-position: 0 -160px; } .emoji-1F1E8-1F1EC { background-position: -20px -160px; } .emoji-1F1E8-1F1ED { background-position: -40px -160px; } .emoji-1F1E8-1F1EE { background-position: -60px -160px; } @@ -79,7 +79,7 @@ .emoji-1F1E8-1F1F2 { background-position: -120px -160px; } .emoji-1F1E8-1F1F3 { background-position: -140px -160px; } .emoji-1F1E8-1F1F4 { background-position: -160px -160px; } -.emoji-1F1E8-1F1F5 { background-position: -180px 0px; } +.emoji-1F1E8-1F1F5 { background-position: -180px 0; } .emoji-1F1E8-1F1F7 { background-position: -180px -20px; } .emoji-1F1E8-1F1FA { background-position: -180px -40px; } .emoji-1F1E8-1F1FB { background-position: -180px -60px; } @@ -88,7 +88,7 @@ .emoji-1F1E8-1F1FE { background-position: -180px -120px; } .emoji-1F1E8-1F1FF { background-position: -180px -140px; } .emoji-1F1E9-1F1EA { background-position: -180px -160px; } -.emoji-1F1E9-1F1EC { background-position: 0px -180px; } +.emoji-1F1E9-1F1EC { background-position: 0 -180px; } .emoji-1F1E9-1F1EF { background-position: -20px -180px; } .emoji-1F1E9-1F1F0 { background-position: -40px -180px; } .emoji-1F1E9-1F1F2 { background-position: -60px -180px; } @@ -98,7 +98,7 @@ .emoji-1F1EA-1F1E8 { background-position: -140px -180px; } .emoji-1F1EA-1F1EA { background-position: -160px -180px; } .emoji-1F1EA-1F1EC { background-position: -180px -180px; } -.emoji-1F1EA-1F1ED { background-position: -200px 0px; } +.emoji-1F1EA-1F1ED { background-position: -200px 0; } .emoji-1F1EA-1F1F7 { background-position: -200px -20px; } .emoji-1F1EA-1F1F8 { background-position: -200px -40px; } .emoji-1F1EA-1F1F9 { background-position: -200px -60px; } @@ -108,7 +108,7 @@ .emoji-1F1EB-1F1F0 { background-position: -200px -140px; } .emoji-1F1EB-1F1F2 { background-position: -200px -160px; } .emoji-1F1EB-1F1F4 { background-position: -200px -180px; } -.emoji-1F1EB-1F1F7 { background-position: 0px -200px; } +.emoji-1F1EB-1F1F7 { background-position: 0 -200px; } .emoji-1F1EC-1F1E6 { background-position: -20px -200px; } .emoji-1F1EC-1F1E7 { background-position: -40px -200px; } .emoji-1F1EC-1F1E9 { background-position: -60px -200px; } @@ -119,7 +119,7 @@ .emoji-1F1EC-1F1EE { background-position: -160px -200px; } .emoji-1F1EC-1F1F1 { background-position: -180px -200px; } .emoji-1F1EC-1F1F2 { background-position: -200px -200px; } -.emoji-1F1EC-1F1F3 { background-position: -220px 0px; } +.emoji-1F1EC-1F1F3 { background-position: -220px 0; } .emoji-1F1EC-1F1F5 { background-position: -220px -20px; } .emoji-1F1EC-1F1F6 { background-position: -220px -40px; } .emoji-1F1EC-1F1F7 { background-position: -220px -60px; } @@ -130,7 +130,7 @@ .emoji-1F1EC-1F1FE { background-position: -220px -160px; } .emoji-1F1ED-1F1F0 { background-position: -220px -180px; } .emoji-1F1ED-1F1F2 { background-position: -220px -200px; } -.emoji-1F1ED-1F1F3 { background-position: 0px -220px; } +.emoji-1F1ED-1F1F3 { background-position: 0 -220px; } .emoji-1F1ED-1F1F7 { background-position: -20px -220px; } .emoji-1F1ED-1F1F9 { background-position: -40px -220px; } .emoji-1F1ED-1F1FA { background-position: -60px -220px; } @@ -142,7 +142,7 @@ .emoji-1F1EE-1F1F3 { background-position: -180px -220px; } .emoji-1F1EE-1F1F4 { background-position: -200px -220px; } .emoji-1F1EE-1F1F6 { background-position: -220px -220px; } -.emoji-1F1EE-1F1F7 { background-position: -240px 0px; } +.emoji-1F1EE-1F1F7 { background-position: -240px 0; } .emoji-1F1EE-1F1F8 { background-position: -240px -20px; } .emoji-1F1EE-1F1F9 { background-position: -240px -40px; } .emoji-1F1EF-1F1EA { background-position: -240px -60px; } @@ -154,7 +154,7 @@ .emoji-1F1F0-1F1ED { background-position: -240px -180px; } .emoji-1F1F0-1F1EE { background-position: -240px -200px; } .emoji-1F1F0-1F1F2 { background-position: -240px -220px; } -.emoji-1F1F0-1F1F3 { background-position: 0px -240px; } +.emoji-1F1F0-1F1F3 { background-position: 0 -240px; } .emoji-1F1F0-1F1F5 { background-position: -20px -240px; } .emoji-1F1F0-1F1F7 { background-position: -40px -240px; } .emoji-1F1F0-1F1FC { background-position: -60px -240px; } @@ -167,7 +167,7 @@ .emoji-1F1F1-1F1F0 { background-position: -200px -240px; } .emoji-1F1F1-1F1F7 { background-position: -220px -240px; } .emoji-1F1F1-1F1F8 { background-position: -240px -240px; } -.emoji-1F1F1-1F1F9 { background-position: -260px 0px; } +.emoji-1F1F1-1F1F9 { background-position: -260px 0; } .emoji-1F1F1-1F1FA { background-position: -260px -20px; } .emoji-1F1F1-1F1FB { background-position: -260px -40px; } .emoji-1F1F1-1F1FE { background-position: -260px -60px; } @@ -180,7 +180,7 @@ .emoji-1F1F2-1F1ED { background-position: -260px -200px; } .emoji-1F1F2-1F1F0 { background-position: -260px -220px; } .emoji-1F1F2-1F1F1 { background-position: -260px -240px; } -.emoji-1F1F2-1F1F2 { background-position: 0px -260px; } +.emoji-1F1F2-1F1F2 { background-position: 0 -260px; } .emoji-1F1F2-1F1F3 { background-position: -20px -260px; } .emoji-1F1F2-1F1F4 { background-position: -40px -260px; } .emoji-1F1F2-1F1F5 { background-position: -60px -260px; } @@ -194,7 +194,7 @@ .emoji-1F1F2-1F1FD { background-position: -220px -260px; } .emoji-1F1F2-1F1FE { background-position: -240px -260px; } .emoji-1F1F2-1F1FF { background-position: -260px -260px; } -.emoji-1F1F3-1F1E6 { background-position: -280px 0px; } +.emoji-1F1F3-1F1E6 { background-position: -280px 0; } .emoji-1F1F3-1F1E8 { background-position: -280px -20px; } .emoji-1F1F3-1F1EA { background-position: -280px -40px; } .emoji-1F1F3-1F1EB { background-position: -280px -60px; } @@ -208,7 +208,7 @@ .emoji-1F1F3-1F1FF { background-position: -280px -220px; } .emoji-1F1F4-1F1F2 { background-position: -280px -240px; } .emoji-1F1F5-1F1E6 { background-position: -280px -260px; } -.emoji-1F1F5-1F1EA { background-position: 0px -280px; } +.emoji-1F1F5-1F1EA { background-position: 0 -280px; } .emoji-1F1F5-1F1EB { background-position: -20px -280px; } .emoji-1F1F5-1F1EC { background-position: -40px -280px; } .emoji-1F1F5-1F1ED { background-position: -60px -280px; } @@ -223,7 +223,7 @@ .emoji-1F1F5-1F1FE { background-position: -240px -280px; } .emoji-1F1F6-1F1E6 { background-position: -260px -280px; } .emoji-1F1F7-1F1EA { background-position: -280px -280px; } -.emoji-1F1F7-1F1F4 { background-position: -300px 0px; } +.emoji-1F1F7-1F1F4 { background-position: -300px 0; } .emoji-1F1F7-1F1F8 { background-position: -300px -20px; } .emoji-1F1F7-1F1FA { background-position: -300px -40px; } .emoji-1F1F7-1F1FC { background-position: -300px -60px; } @@ -238,7 +238,7 @@ .emoji-1F1F8-1F1EF { background-position: -300px -240px; } .emoji-1F1F8-1F1F0 { background-position: -300px -260px; } .emoji-1F1F8-1F1F1 { background-position: -300px -280px; } -.emoji-1F1F8-1F1F2 { background-position: 0px -300px; } +.emoji-1F1F8-1F1F2 { background-position: 0 -300px; } .emoji-1F1F8-1F1F3 { background-position: -20px -300px; } .emoji-1F1F8-1F1F4 { background-position: -40px -300px; } .emoji-1F1F8-1F1F7 { background-position: -60px -300px; } @@ -254,7 +254,7 @@ .emoji-1F1F9-1F1EB { background-position: -260px -300px; } .emoji-1F1F9-1F1EC { background-position: -280px -300px; } .emoji-1F1F9-1F1ED { background-position: -300px -300px; } -.emoji-1F1F9-1F1EF { background-position: -320px 0px; } +.emoji-1F1F9-1F1EF { background-position: -320px 0; } .emoji-1F1F9-1F1F0 { background-position: -320px -20px; } .emoji-1F1F9-1F1F1 { background-position: -320px -40px; } .emoji-1F1F9-1F1F2 { background-position: -320px -60px; } @@ -270,7 +270,7 @@ .emoji-1F1FA-1F1F2 { background-position: -320px -260px; } .emoji-1F1FA-1F1F8 { background-position: -320px -280px; } .emoji-1F1FA-1F1FE { background-position: -320px -300px; } -.emoji-1F1FA-1F1FF { background-position: 0px -320px; } +.emoji-1F1FA-1F1FF { background-position: 0 -320px; } .emoji-1F1FB-1F1E6 { background-position: -20px -320px; } .emoji-1F1FB-1F1E8 { background-position: -40px -320px; } .emoji-1F1FB-1F1EA { background-position: -60px -320px; } @@ -287,7 +287,7 @@ .emoji-1F1FF-1F1F2 { background-position: -280px -320px; } .emoji-1F1FF-1F1FC { background-position: -300px -320px; } .emoji-1F201 { background-position: -320px -320px; } -.emoji-1F202 { background-position: -340px 0px; } +.emoji-1F202 { background-position: -340px 0; } .emoji-1F21A { background-position: -340px -20px; } .emoji-1F22F { background-position: -340px -40px; } .emoji-1F232 { background-position: -340px -60px; } @@ -304,7 +304,7 @@ .emoji-1F300 { background-position: -340px -280px; } .emoji-1F301 { background-position: -340px -300px; } .emoji-1F302 { background-position: -340px -320px; } -.emoji-1F303 { background-position: 0px -340px; } +.emoji-1F303 { background-position: 0 -340px; } .emoji-1F304 { background-position: -20px -340px; } .emoji-1F305 { background-position: -40px -340px; } .emoji-1F306 { background-position: -60px -340px; } @@ -322,7 +322,7 @@ .emoji-1F312 { background-position: -300px -340px; } .emoji-1F313 { background-position: -320px -340px; } .emoji-1F314 { background-position: -340px -340px; } -.emoji-1F315 { background-position: -360px 0px; } +.emoji-1F315 { background-position: -360px 0; } .emoji-1F316 { background-position: -360px -20px; } .emoji-1F317 { background-position: -360px -40px; } .emoji-1F318 { background-position: -360px -60px; } @@ -340,7 +340,7 @@ .emoji-1F326 { background-position: -360px -300px; } .emoji-1F327 { background-position: -360px -320px; } .emoji-1F328 { background-position: -360px -340px; } -.emoji-1F329 { background-position: 0px -360px; } +.emoji-1F329 { background-position: 0 -360px; } .emoji-1F32A { background-position: -20px -360px; } .emoji-1F32B { background-position: -40px -360px; } .emoji-1F32C { background-position: -60px -360px; } @@ -359,7 +359,7 @@ .emoji-1F339 { background-position: -320px -360px; } .emoji-1F33A { background-position: -340px -360px; } .emoji-1F33B { background-position: -360px -360px; } -.emoji-1F33C { background-position: -380px 0px; } +.emoji-1F33C { background-position: -380px 0; } .emoji-1F33D { background-position: -380px -20px; } .emoji-1F33E { background-position: -380px -40px; } .emoji-1F33F { background-position: -380px -60px; } @@ -378,7 +378,7 @@ .emoji-1F34C { background-position: -380px -320px; } .emoji-1F34D { background-position: -380px -340px; } .emoji-1F34E { background-position: -380px -360px; } -.emoji-1F34F { background-position: 0px -380px; } +.emoji-1F34F { background-position: 0 -380px; } .emoji-1F350 { background-position: -20px -380px; } .emoji-1F351 { background-position: -40px -380px; } .emoji-1F352 { background-position: -60px -380px; } @@ -398,7 +398,7 @@ .emoji-1F360 { background-position: -340px -380px; } .emoji-1F361 { background-position: -360px -380px; } .emoji-1F362 { background-position: -380px -380px; } -.emoji-1F363 { background-position: -400px 0px; } +.emoji-1F363 { background-position: -400px 0; } .emoji-1F364 { background-position: -400px -20px; } .emoji-1F365 { background-position: -400px -40px; } .emoji-1F366 { background-position: -400px -60px; } @@ -418,7 +418,7 @@ .emoji-1F374 { background-position: -400px -340px; } .emoji-1F375 { background-position: -400px -360px; } .emoji-1F376 { background-position: -400px -380px; } -.emoji-1F377 { background-position: 0px -400px; } +.emoji-1F377 { background-position: 0 -400px; } .emoji-1F378 { background-position: -20px -400px; } .emoji-1F379 { background-position: -40px -400px; } .emoji-1F37A { background-position: -60px -400px; } @@ -439,7 +439,7 @@ .emoji-1F385-1F3FE { background-position: -360px -400px; } .emoji-1F385-1F3FF { background-position: -380px -400px; } .emoji-1F386 { background-position: -400px -400px; } -.emoji-1F387 { background-position: -420px 0px; } +.emoji-1F387 { background-position: -420px 0; } .emoji-1F388 { background-position: -420px -20px; } .emoji-1F389 { background-position: -420px -40px; } .emoji-1F38A { background-position: -420px -60px; } @@ -460,7 +460,7 @@ .emoji-1F399 { background-position: -420px -360px; } .emoji-1F39A { background-position: -420px -380px; } .emoji-1F39B { background-position: -420px -400px; } -.emoji-1F39C { background-position: 0px -420px; } +.emoji-1F39C { background-position: 0 -420px; } .emoji-1F39D { background-position: -20px -420px; } .emoji-1F39E { background-position: -40px -420px; } .emoji-1F39F { background-position: -60px -420px; } @@ -482,7 +482,7 @@ .emoji-1F3AF { background-position: -380px -420px; } .emoji-1F3B0 { background-position: -400px -420px; } .emoji-1F3B1 { background-position: -420px -420px; } -.emoji-1F3B2 { background-position: -440px 0px; } +.emoji-1F3B2 { background-position: -440px 0; } .emoji-1F3B3 { background-position: -440px -20px; } .emoji-1F3B4 { background-position: -440px -40px; } .emoji-1F3B5 { background-position: -440px -60px; } @@ -504,7 +504,7 @@ .emoji-1F3C3-1F3FC { background-position: -440px -380px; } .emoji-1F3C3-1F3FD { background-position: -440px -400px; } .emoji-1F3C3-1F3FE { background-position: -440px -420px; } -.emoji-1F3C3-1F3FF { background-position: 0px -440px; } +.emoji-1F3C3-1F3FF { background-position: 0 -440px; } .emoji-1F3C4 { background-position: -20px -440px; } .emoji-1F3C4-1F3FB { background-position: -40px -440px; } .emoji-1F3C4-1F3FC { background-position: -60px -440px; } @@ -527,7 +527,7 @@ .emoji-1F3CA-1F3FD { background-position: -400px -440px; } .emoji-1F3CA-1F3FE { background-position: -420px -440px; } .emoji-1F3CA-1F3FF { background-position: -440px -440px; } -.emoji-1F3CB { background-position: -460px 0px; } +.emoji-1F3CB { background-position: -460px 0; } .emoji-1F3CB-1F3FB { background-position: -460px -20px; } .emoji-1F3CB-1F3FC { background-position: -460px -40px; } .emoji-1F3CB-1F3FD { background-position: -460px -60px; } @@ -550,7 +550,7 @@ .emoji-1F3DA { background-position: -460px -400px; } .emoji-1F3DB { background-position: -460px -420px; } .emoji-1F3DC { background-position: -460px -440px; } -.emoji-1F3DD { background-position: 0px -460px; } +.emoji-1F3DD { background-position: 0 -460px; } .emoji-1F3DE { background-position: -20px -460px; } .emoji-1F3DF { background-position: -40px -460px; } .emoji-1F3E0 { background-position: -60px -460px; } @@ -574,7 +574,7 @@ .emoji-1F3F2 { background-position: -420px -460px; } .emoji-1F3F3 { background-position: -440px -460px; } .emoji-1F3F4 { background-position: -460px -460px; } -.emoji-1F3F5 { background-position: -480px 0px; } +.emoji-1F3F5 { background-position: -480px 0; } .emoji-1F3F6 { background-position: -480px -20px; } .emoji-1F3F7 { background-position: -480px -40px; } .emoji-1F3F8 { background-position: -480px -60px; } @@ -598,7 +598,7 @@ .emoji-1F40A { background-position: -480px -420px; } .emoji-1F40B { background-position: -480px -440px; } .emoji-1F40C { background-position: -480px -460px; } -.emoji-1F40D { background-position: 0px -480px; } +.emoji-1F40D { background-position: 0 -480px; } .emoji-1F40E { background-position: -20px -480px; } .emoji-1F40F { background-position: -40px -480px; } .emoji-1F410 { background-position: -60px -480px; } @@ -623,7 +623,7 @@ .emoji-1F423 { background-position: -440px -480px; } .emoji-1F424 { background-position: -460px -480px; } .emoji-1F425 { background-position: -480px -480px; } -.emoji-1F426 { background-position: -500px 0px; } +.emoji-1F426 { background-position: -500px 0; } .emoji-1F427 { background-position: -500px -20px; } .emoji-1F428 { background-position: -500px -40px; } .emoji-1F429 { background-position: -500px -60px; } @@ -648,7 +648,7 @@ .emoji-1F43C { background-position: -500px -440px; } .emoji-1F43D { background-position: -500px -460px; } .emoji-1F43E { background-position: -500px -480px; } -.emoji-1F43F { background-position: 0px -500px; } +.emoji-1F43F { background-position: 0 -500px; } .emoji-1F440 { background-position: -20px -500px; } .emoji-1F441 { background-position: -40px -500px; } .emoji-1F441-1F5E8 { background-position: -60px -500px; } @@ -674,7 +674,7 @@ .emoji-1F446-1F3FF { background-position: -460px -500px; } .emoji-1F447 { background-position: -480px -500px; } .emoji-1F447-1F3FB { background-position: -500px -500px; } -.emoji-1F447-1F3FC { background-position: -520px 0px; } +.emoji-1F447-1F3FC { background-position: -520px 0; } .emoji-1F447-1F3FD { background-position: -520px -20px; } .emoji-1F447-1F3FE { background-position: -520px -40px; } .emoji-1F447-1F3FF { background-position: -520px -60px; } @@ -700,7 +700,7 @@ .emoji-1F44B-1F3FB { background-position: -520px -460px; } .emoji-1F44B-1F3FC { background-position: -520px -480px; } .emoji-1F44B-1F3FD { background-position: -520px -500px; } -.emoji-1F44B-1F3FE { background-position: 0px -520px; } +.emoji-1F44B-1F3FE { background-position: 0 -520px; } .emoji-1F44B-1F3FF { background-position: -20px -520px; } .emoji-1F44C { background-position: -40px -520px; } .emoji-1F44C-1F3FB { background-position: -60px -520px; } @@ -727,7 +727,7 @@ .emoji-1F44F-1F3FE { background-position: -480px -520px; } .emoji-1F44F-1F3FF { background-position: -500px -520px; } .emoji-1F450 { background-position: -520px -520px; } -.emoji-1F450-1F3FB { background-position: -540px 0px; } +.emoji-1F450-1F3FB { background-position: -540px 0; } .emoji-1F450-1F3FC { background-position: -540px -20px; } .emoji-1F450-1F3FD { background-position: -540px -40px; } .emoji-1F450-1F3FE { background-position: -540px -60px; } @@ -754,7 +754,7 @@ .emoji-1F464 { background-position: -540px -480px; } .emoji-1F465 { background-position: -540px -500px; } .emoji-1F466 { background-position: -540px -520px; } -.emoji-1F466-1F3FB { background-position: 0px -540px; } +.emoji-1F466-1F3FB { background-position: 0 -540px; } .emoji-1F466-1F3FC { background-position: -20px -540px; } .emoji-1F466-1F3FD { background-position: -40px -540px; } .emoji-1F466-1F3FE { background-position: -60px -540px; } @@ -782,7 +782,7 @@ .emoji-1F468-1F469-1F467-1F467 { background-position: -500px -540px; } .emoji-1F468-2764-1F468 { background-position: -520px -540px; } .emoji-1F468-2764-1F48B-1F468 { background-position: -540px -540px; } -.emoji-1F469 { background-position: -560px 0px; } +.emoji-1F469 { background-position: -560px 0; } .emoji-1F469-1F3FB { background-position: -560px -20px; } .emoji-1F469-1F3FC { background-position: -560px -40px; } .emoji-1F469-1F3FD { background-position: -560px -60px; } @@ -810,7 +810,7 @@ .emoji-1F470-1F3FB { background-position: -560px -500px; } .emoji-1F470-1F3FC { background-position: -560px -520px; } .emoji-1F470-1F3FD { background-position: -560px -540px; } -.emoji-1F470-1F3FE { background-position: 0px -560px; } +.emoji-1F470-1F3FE { background-position: 0 -560px; } .emoji-1F470-1F3FF { background-position: -20px -560px; } .emoji-1F471 { background-position: -40px -560px; } .emoji-1F471-1F3FB { background-position: -60px -560px; } @@ -839,7 +839,7 @@ .emoji-1F475 { background-position: -520px -560px; } .emoji-1F475-1F3FB { background-position: -540px -560px; } .emoji-1F475-1F3FC { background-position: -560px -560px; } -.emoji-1F475-1F3FD { background-position: -580px 0px; } +.emoji-1F475-1F3FD { background-position: -580px 0; } .emoji-1F475-1F3FE { background-position: -580px -20px; } .emoji-1F475-1F3FF { background-position: -580px -40px; } .emoji-1F476 { background-position: -580px -60px; } @@ -868,7 +868,7 @@ .emoji-1F47C-1F3FC { background-position: -580px -520px; } .emoji-1F47C-1F3FD { background-position: -580px -540px; } .emoji-1F47C-1F3FE { background-position: -580px -560px; } -.emoji-1F47C-1F3FF { background-position: 0px -580px; } +.emoji-1F47C-1F3FF { background-position: 0 -580px; } .emoji-1F47D { background-position: -20px -580px; } .emoji-1F47E { background-position: -40px -580px; } .emoji-1F47F { background-position: -60px -580px; } @@ -898,7 +898,7 @@ .emoji-1F485-1F3FD { background-position: -540px -580px; } .emoji-1F485-1F3FE { background-position: -560px -580px; } .emoji-1F485-1F3FF { background-position: -580px -580px; } -.emoji-1F486 { background-position: -600px 0px; } +.emoji-1F486 { background-position: -600px 0; } .emoji-1F486-1F3FB { background-position: -600px -20px; } .emoji-1F486-1F3FC { background-position: -600px -40px; } .emoji-1F486-1F3FD { background-position: -600px -60px; } @@ -928,7 +928,7 @@ .emoji-1F497 { background-position: -600px -540px; } .emoji-1F498 { background-position: -600px -560px; } .emoji-1F499 { background-position: -600px -580px; } -.emoji-1F49A { background-position: 0px -600px; } +.emoji-1F49A { background-position: 0 -600px; } .emoji-1F49B { background-position: -20px -600px; } .emoji-1F49C { background-position: -40px -600px; } .emoji-1F49D { background-position: -60px -600px; } @@ -959,7 +959,7 @@ .emoji-1F4B1 { background-position: -560px -600px; } .emoji-1F4B2 { background-position: -580px -600px; } .emoji-1F4B3 { background-position: -600px -600px; } -.emoji-1F4B4 { background-position: -620px 0px; } +.emoji-1F4B4 { background-position: -620px 0; } .emoji-1F4B5 { background-position: -620px -20px; } .emoji-1F4B6 { background-position: -620px -40px; } .emoji-1F4B7 { background-position: -620px -60px; } @@ -990,7 +990,7 @@ .emoji-1F4D0 { background-position: -620px -560px; } .emoji-1F4D1 { background-position: -620px -580px; } .emoji-1F4D2 { background-position: -620px -600px; } -.emoji-1F4D3 { background-position: 0px -620px; } +.emoji-1F4D3 { background-position: 0 -620px; } .emoji-1F4D4 { background-position: -20px -620px; } .emoji-1F4D5 { background-position: -40px -620px; } .emoji-1F4D6 { background-position: -60px -620px; } @@ -1022,7 +1022,7 @@ .emoji-1F4F0 { background-position: -580px -620px; } .emoji-1F4F1 { background-position: -600px -620px; } .emoji-1F4F2 { background-position: -620px -620px; } -.emoji-1F4F3 { background-position: -640px 0px; } +.emoji-1F4F3 { background-position: -640px 0; } .emoji-1F4F4 { background-position: -640px -20px; } .emoji-1F4F5 { background-position: -640px -40px; } .emoji-1F4F6 { background-position: -640px -60px; } @@ -1054,7 +1054,7 @@ .emoji-1F510 { background-position: -640px -580px; } .emoji-1F511 { background-position: -640px -600px; } .emoji-1F512 { background-position: -640px -620px; } -.emoji-1F513 { background-position: 0px -640px; } +.emoji-1F513 { background-position: 0 -640px; } .emoji-1F514 { background-position: -20px -640px; } .emoji-1F515 { background-position: -40px -640px; } .emoji-1F516 { background-position: -60px -640px; } @@ -1087,7 +1087,7 @@ .emoji-1F531 { background-position: -600px -640px; } .emoji-1F532 { background-position: -620px -640px; } .emoji-1F533 { background-position: -640px -640px; } -.emoji-1F534 { background-position: -660px 0px; } +.emoji-1F534 { background-position: -660px 0; } .emoji-1F535 { background-position: -660px -20px; } .emoji-1F536 { background-position: -660px -40px; } .emoji-1F537 { background-position: -660px -60px; } @@ -1120,7 +1120,7 @@ .emoji-1F55B { background-position: -660px -600px; } .emoji-1F55C { background-position: -660px -620px; } .emoji-1F55D { background-position: -660px -640px; } -.emoji-1F55E { background-position: 0px -660px; } +.emoji-1F55E { background-position: 0 -660px; } .emoji-1F55F { background-position: -20px -660px; } .emoji-1F560 { background-position: -40px -660px; } .emoji-1F561 { background-position: -60px -660px; } @@ -1154,7 +1154,7 @@ .emoji-1F578 { background-position: -620px -660px; } .emoji-1F579 { background-position: -640px -660px; } .emoji-1F57B { background-position: -660px -660px; } -.emoji-1F57E { background-position: -680px 0px; } +.emoji-1F57E { background-position: -680px 0; } .emoji-1F57F { background-position: -680px -20px; } .emoji-1F581 { background-position: -680px -40px; } .emoji-1F582 { background-position: -680px -60px; } @@ -1188,7 +1188,7 @@ .emoji-1F595-1F3FF { background-position: -680px -620px; } .emoji-1F596 { background-position: -680px -640px; } .emoji-1F596-1F3FB { background-position: -680px -660px; } -.emoji-1F596-1F3FC { background-position: 0px -680px; } +.emoji-1F596-1F3FC { background-position: 0 -680px; } .emoji-1F596-1F3FD { background-position: -20px -680px; } .emoji-1F596-1F3FE { background-position: -40px -680px; } .emoji-1F596-1F3FF { background-position: -60px -680px; } @@ -1223,7 +1223,7 @@ .emoji-1F5C4 { background-position: -640px -680px; } .emoji-1F5C6 { background-position: -660px -680px; } .emoji-1F5C7 { background-position: -680px -680px; } -.emoji-1F5C9 { background-position: -700px 0px; } +.emoji-1F5C9 { background-position: -700px 0; } .emoji-1F5CA { background-position: -700px -20px; } .emoji-1F5CE { background-position: -700px -40px; } .emoji-1F5CF { background-position: -700px -60px; } @@ -1258,7 +1258,7 @@ .emoji-1F5F8 { background-position: -700px -640px; } .emoji-1F5F9 { background-position: -700px -660px; } .emoji-1F5FA { background-position: -700px -680px; } -.emoji-1F5FB { background-position: 0px -700px; } +.emoji-1F5FB { background-position: 0 -700px; } .emoji-1F5FC { background-position: -20px -700px; } .emoji-1F5FD { background-position: -40px -700px; } .emoji-1F5FE { background-position: -60px -700px; } @@ -1294,7 +1294,7 @@ .emoji-1F61C { background-position: -660px -700px; } .emoji-1F61D { background-position: -680px -700px; } .emoji-1F61E { background-position: -700px -700px; } -.emoji-1F61F { background-position: -720px 0px; } +.emoji-1F61F { background-position: -720px 0; } .emoji-1F620 { background-position: -720px -20px; } .emoji-1F621 { background-position: -720px -40px; } .emoji-1F622 { background-position: -720px -60px; } @@ -1330,7 +1330,7 @@ .emoji-1F640 { background-position: -720px -660px; } .emoji-1F641 { background-position: -720px -680px; } .emoji-1F642 { background-position: -720px -700px; } -.emoji-1F643 { background-position: 0px -720px; } +.emoji-1F643 { background-position: 0 -720px; } .emoji-1F644 { background-position: -20px -720px; } .emoji-1F645 { background-position: -40px -720px; } .emoji-1F645-1F3FB { background-position: -60px -720px; } @@ -1367,7 +1367,7 @@ .emoji-1F64C-1F3FF { background-position: -680px -720px; } .emoji-1F64D { background-position: -700px -720px; } .emoji-1F64D-1F3FB { background-position: -720px -720px; } -.emoji-1F64D-1F3FC { background-position: -740px 0px; } +.emoji-1F64D-1F3FC { background-position: -740px 0; } .emoji-1F64D-1F3FD { background-position: -740px -20px; } .emoji-1F64D-1F3FE { background-position: -740px -40px; } .emoji-1F64D-1F3FF { background-position: -740px -60px; } @@ -1404,7 +1404,7 @@ .emoji-1F692 { background-position: -740px -680px; } .emoji-1F693 { background-position: -740px -700px; } .emoji-1F694 { background-position: -740px -720px; } -.emoji-1F695 { background-position: 0px -740px; } +.emoji-1F695 { background-position: 0 -740px; } .emoji-1F696 { background-position: -20px -740px; } .emoji-1F697 { background-position: -40px -740px; } .emoji-1F698 { background-position: -60px -740px; } @@ -1442,7 +1442,7 @@ .emoji-1F6B3 { background-position: -700px -740px; } .emoji-1F6B4 { background-position: -720px -740px; } .emoji-1F6B4-1F3FB { background-position: -740px -740px; } -.emoji-1F6B4-1F3FC { background-position: -760px 0px; } +.emoji-1F6B4-1F3FC { background-position: -760px 0; } .emoji-1F6B4-1F3FD { background-position: -760px -20px; } .emoji-1F6B4-1F3FE { background-position: -760px -40px; } .emoji-1F6B4-1F3FF { background-position: -760px -60px; } @@ -1480,7 +1480,7 @@ .emoji-1F6C5 { background-position: -760px -700px; } .emoji-1F6C6 { background-position: -760px -720px; } .emoji-1F6C7 { background-position: -760px -740px; } -.emoji-1F6C8 { background-position: 0px -760px; } +.emoji-1F6C8 { background-position: 0 -760px; } .emoji-1F6C9 { background-position: -20px -760px; } .emoji-1F6CA { background-position: -40px -760px; } .emoji-1F6CB { background-position: -60px -760px; } @@ -1519,7 +1519,7 @@ .emoji-1F918-1F3FC { background-position: -720px -760px; } .emoji-1F918-1F3FD { background-position: -740px -760px; } .emoji-1F918-1F3FE { background-position: -760px -760px; } -.emoji-1F918-1F3FF { background-position: -780px 0px; } +.emoji-1F918-1F3FF { background-position: -780px 0; } .emoji-1F980 { background-position: -780px -20px; } .emoji-1F981 { background-position: -780px -40px; } .emoji-1F982 { background-position: -780px -60px; } @@ -1558,7 +1558,7 @@ .emoji-24C2 { background-position: -780px -720px; } .emoji-25AA { background-position: -780px -740px; } .emoji-25AB { background-position: -780px -760px; } -.emoji-25B6 { background-position: 0px -780px; } +.emoji-25B6 { background-position: 0 -780px; } .emoji-25C0 { background-position: -20px -780px; } .emoji-25FB { background-position: -40px -780px; } .emoji-25FC { background-position: -60px -780px; } @@ -1598,7 +1598,7 @@ .emoji-264D { background-position: -740px -780px; } .emoji-264E { background-position: -760px -780px; } .emoji-264F { background-position: -780px -780px; } -.emoji-2650 { background-position: -800px 0px; } +.emoji-2650 { background-position: -800px 0; } .emoji-2651 { background-position: -800px -20px; } .emoji-2652 { background-position: -800px -40px; } .emoji-2653 { background-position: -800px -60px; } @@ -1638,7 +1638,7 @@ .emoji-26F0 { background-position: -800px -740px; } .emoji-26F1 { background-position: -800px -760px; } .emoji-26F2 { background-position: -800px -780px; } -.emoji-26F3 { background-position: 0px -800px; } +.emoji-26F3 { background-position: 0 -800px; } .emoji-26F4 { background-position: -20px -800px; } .emoji-26F5 { background-position: -40px -800px; } .emoji-26F7 { background-position: -60px -800px; } @@ -1679,7 +1679,7 @@ .emoji-270D-1F3FD { background-position: -760px -800px; } .emoji-270D-1F3FE { background-position: -780px -800px; } .emoji-270D-1F3FF { background-position: -800px -800px; } -.emoji-270F { background-position: -820px 0px; } +.emoji-270F { background-position: -820px 0; } .emoji-2712 { background-position: -820px -20px; } .emoji-2714 { background-position: -820px -40px; } .emoji-2716 { background-position: -820px -60px; } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 1833e2136b..e7da0a2f68 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -63,7 +63,7 @@ .note-image-attach { margin-top: 4px; - margin-left: 0px; + margin-left: 0; max-width: 200px; float: none; } diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss index 3df4bb84bd..6a99cd9cb9 100644 --- a/app/assets/stylesheets/pages/import.scss +++ b/app/assets/stylesheets/pages/import.scss @@ -1,6 +1,6 @@ i.icon-gitorious { display: inline-block; - background-position: 0px 0px; + background-position: 0 0; background-size: contain; background-repeat: no-repeat; } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 6c35a74f08..d9c4788126 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -39,7 +39,7 @@ .login-box{ background: #fafafa; border-radius: 10px; - box-shadow: 0 0px 2px #ccc; + box-shadow: 0 0 2px #ccc; padding: 15px; .login-heading h3 { @@ -74,7 +74,7 @@ &.top { @include border-radius(5px 5px 0 0); - margin-bottom: 0px; + margin-bottom: 0; } &.bottom { @@ -85,7 +85,7 @@ &.middle { border-top: 0; - margin-bottom:0px; + margin-bottom:0; @include border-radius(0); } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 1f631ab03e..969c79a9be 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -11,8 +11,8 @@ ul.notes { display: block; list-style: none; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; .timeline-icon { float: left; diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index ecfe0e37c8..260179074c 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -30,7 +30,7 @@ .btn { line-height: 40px; height: 42px; - padding: 0px 12px; + padding: 0 12px; img { width: 32px; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index a29b735eb0..3fe2c9a334 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -85,7 +85,7 @@ .project-repo-buttons { margin-top: 20px; - margin-bottom: 0px; + margin-bottom: 0; .count-buttons { display: block; @@ -162,7 +162,7 @@ cursor: pointer; background-image: none; white-space: nowrap; - margin: 0 11px 0px 4px; + margin: 0 11px 0 4px; &:hover { background: #fff; @@ -210,7 +210,7 @@ } .project_member_row form { - margin: 0px; + margin: 0; } .transfer-project .select2-container { diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss index 92d84d9640..bed6470dbd 100644 --- a/app/assets/stylesheets/pages/sherlock.scss +++ b/app/assets/stylesheets/pages/sherlock.scss @@ -13,13 +13,13 @@ table .sherlock-code { } .sherlock-line-samples-table { - margin-bottom: 0px !important; + margin-bottom: 0 !important; thead tr th, tbody tr td { font-size: 13px !important; text-align: right; - padding: 0px 10px !important; + padding: 0 10px !important; } } diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 2ab40941c4..27970eba15 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -61,7 +61,7 @@ .note-image-attach { margin-top: 4px; - margin-left: 0px; + margin-left: 0; max-width: 200px; float: none; } diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake index ebe301c1fc..cfaf4a129b 100644 --- a/lib/tasks/gemojione.rake +++ b/lib/tasks/gemojione.rake @@ -47,6 +47,7 @@ namespace :gemojione do # let's simplify it system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path})) system(%Q(sed -i '' "s/ no-repeat//" #{style_path})) + system(%Q(sed -i '' "s/ 0px/ 0/" #{style_path})) # Append a generic rule that applies to all Emojis File.open(style_path, 'a') do |f| From 5d3cd7fd3783d7e94894431ec13843a03feb77a5 Mon Sep 17 00:00:00 2001 From: connorshea Date: Wed, 16 Mar 2016 17:40:12 -0600 Subject: [PATCH 057/153] Follow the CSS Style Guide rules for including a space after the property colon. Fixes violations of SpaceAfterPropertyColon. Discussed in #14299. [ci skip] --- app/assets/stylesheets/framework/common.scss | 24 +++++++++---------- .../stylesheets/framework/variables.scss | 2 +- app/assets/stylesheets/pages/appearances.scss | 2 +- app/assets/stylesheets/pages/dashboard.scss | 6 ++--- app/assets/stylesheets/pages/events.scss | 4 ++-- app/assets/stylesheets/pages/issuable.scss | 2 +- app/assets/stylesheets/pages/login.scss | 4 ++-- app/assets/stylesheets/pages/notes.scss | 10 ++++---- app/assets/stylesheets/pages/projects.scss | 8 +++---- app/assets/stylesheets/pages/tree.scss | 2 +- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 180926b3b9..bc03c2180b 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -8,20 +8,20 @@ /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } .prepend-top-5 { margin-top: 5px; } -.prepend-top-10 { margin-top:10px } +.prepend-top-10 { margin-top: 10px } .prepend-top-default { margin-top: $gl-padding !important; } -.prepend-top-20 { margin-top:20px } -.prepend-left-10 { margin-left:10px } +.prepend-top-20 { margin-top: 20px } +.prepend-left-10 { margin-left: 10px } .prepend-left-default { margin-left: $gl-padding; } -.prepend-left-20 { margin-left:20px } +.prepend-left-20 { margin-left: 20px } .append-right-5 { margin-right: 5px } -.append-right-10 { margin-right:10px } +.append-right-10 { margin-right: 10px } .append-right-default { margin-right: $gl-padding; } -.append-right-20 { margin-right:20px } -.append-bottom-0 { margin-bottom:0 } -.append-bottom-10 { margin-bottom:10px } -.append-bottom-15 { margin-bottom:15px } -.append-bottom-20 { margin-bottom:20px } +.append-right-20 { margin-right: 20px } +.append-bottom-0 { margin-bottom: 0 } +.append-bottom-10 { margin-bottom: 10px } +.append-bottom-15 { margin-bottom: 15px } +.append-bottom-20 { margin-bottom: 20px } .append-bottom-default { margin-bottom: $gl-padding; } .inline { display: inline-block } .center { text-align: center } @@ -134,10 +134,10 @@ p.time { // Fix issue with notes & lists creating a bunch of bottom borders. li.note { - img { max-width:100% } + img { max-width: 100% } .note-title { li { - border-bottom:none !important; + border-bottom: none !important; } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 5e3546bc6f..211ead7319 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -27,7 +27,7 @@ $gl-gray: #5a5a5a; $gl-padding: 16px; $gl-btn-padding: 10px; $gl-vert-padding: 6px; -$gl-padding-top:10px; +$gl-padding-top: 10px; $gl-avatar-size: 40px; $secondary-text: #7f8fa4; $error-exclamation-point: #e62958; diff --git a/app/assets/stylesheets/pages/appearances.scss b/app/assets/stylesheets/pages/appearances.scss index e2070f17c3..878f44116b 100644 --- a/app/assets/stylesheets/pages/appearances.scss +++ b/app/assets/stylesheets/pages/appearances.scss @@ -4,7 +4,7 @@ } .appearance-light-logo-preview { - background-color: $background-color; + background-color: $background-color; max-width: 72px; padding: 10px; margin-bottom: 10px; diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 8863939914..cf7567513e 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -11,15 +11,15 @@ } .dashboard-search-filter { - padding:5px; + padding: 5px; .search-text-input { - float:left; + float: left; @extend .col-md-2; } .btn { margin-left: 5px; - float:left; + float: left; } } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index e7da0a2f68..b39a9abf40 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -94,7 +94,7 @@ } } - &:last-child { border:none } + &:last-child { border: none } .event_commits { li { @@ -138,7 +138,7 @@ @include str-truncated(100%); padding: 5px 0; font-size: 13px; - float:left; + float: left; margin-right: -150px; padding-right: 150px; line-height: 20px; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index faa2ebfda7..c975ca0ce4 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -59,7 +59,7 @@ .issuable-sidebar { .block { @include clearfix; - padding: $gl-padding 0; + padding: $gl-padding 0; border-bottom: 1px solid $border-gray-light; // This prevents the mess when resizing the sidebar // of elements repositioning themselves.. diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index d9c4788126..bc41f7d306 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -28,7 +28,7 @@ img { max-width: 100%; - margin-bottom: 30px; + margin-bottom: 30px; } a { @@ -85,7 +85,7 @@ &.middle { border-top: 0; - margin-bottom:0; + margin-bottom: 0; @include border-radius(0); } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 969c79a9be..c40dc79537 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -3,9 +3,9 @@ */ @-webkit-keyframes targe3-note { - from { background:#fffff0; } - 50% { background:#ffffd3; } - to { background:#fffff0; } + from { background: #fffff0; } + 50% { background: #ffffd3; } + to { background: #fffff0; } } ul.notes { @@ -93,12 +93,12 @@ ul.notes { .discussion { overflow: hidden; display: block; - position:relative; + position: relative; } .note { display: block; - position:relative; + position: relative; .note-body { overflow: auto; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3fe2c9a334..6c600c99d5 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -286,11 +286,11 @@ table.table.protected-branches-list tr.no-border { padding-bottom: 4px; ul.nav { - display:inline-block; + display: inline-block; } .nav li { - display:inline; + display: inline; } .nav > li > a { @@ -303,11 +303,11 @@ table.table.protected-branches-list tr.no-border { } li { - display:inline; + display: inline; } a { - float:left; + float: left; font-size: 17px; } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index ef63b01060..73c7c9f687 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -46,7 +46,7 @@ img { position: relative; - top:-1px; + top: -1px; } } From f54bf00309e310cabb2fec55d860f0670f3b79ac Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 17 Mar 2016 00:24:12 -0300 Subject: [PATCH 058/153] =?UTF-8?q?Back-porting=20PostReceive=20refactor?= =?UTF-8?q?=20made=20for=20EE=20=F0=9F=8D=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/workers/post_receive.rb | 46 +++++++++++--------------- lib/gitlab/git_post_receive.rb | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 lib/gitlab/git_post_receive.rb diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 14d7813412..3cc232ef1a 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -1,6 +1,5 @@ class PostReceive include Sidekiq::Worker - include Gitlab::Identifier sidekiq_options queue: :post_receive @@ -11,51 +10,44 @@ class PostReceive log("Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"") end - repo_path.gsub!(/\.git\z/, "") - repo_path.gsub!(/\A\//, "") + post_received = Gitlab::GitPostReceive.new(repo_path, identifier, changes) - project = Project.find_with_namespace(repo_path) - - if project.nil? + if post_received.project.nil? log("Triggered hook for non-existing project with full path \"#{repo_path} \"") return false end - changes = Base64.decode64(changes) unless changes.include?(" ") - changes = utf8_encode_changes(changes) - changes = changes.lines + if post_received.wiki? + # Nothing defined here yet. + elsif post_received.regular_project? + process_project_changes(post_received) + else + log("Triggered hook for unidentifiable repository type with full path \"#{repo_path} \"") + false + end + end - changes.each do |change| + def process_project_changes(post_received) + post_received.changes.each do |change| oldrev, newrev, ref = change.strip.split(' ') - @user ||= identify(identifier, project, newrev) + @user ||= post_received.identify(newrev) unless @user - log("Triggered hook for non-existing user \"#{identifier} \"") + log("Triggered hook for non-existing user \"#{post_received.identifier} \"") return false end if Gitlab::Git.tag_ref?(ref) - GitTagPushService.new.execute(project, @user, oldrev, newrev, ref) + GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref) else - GitPushService.new(project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute + GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute end end end - def utf8_encode_changes(changes) - changes = changes.dup - - changes.force_encoding("UTF-8") - return changes if changes.valid_encoding? - - # Convert non-UTF-8 branch/tag names to UTF-8 so they can be dumped as JSON. - detection = CharlockHolmes::EncodingDetector.detect(changes) - return changes unless detection && detection[:encoding] - - CharlockHolmes::Converter.convert(changes, detection[:encoding], 'UTF-8') - end - + private + def log(message) Gitlab::GitLogger.error("POST-RECEIVE: #{message}") end diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb new file mode 100644 index 0000000000..a088e19d1e --- /dev/null +++ b/lib/gitlab/git_post_receive.rb @@ -0,0 +1,60 @@ +module Gitlab + class GitPostReceive + include Gitlab::Identifier + attr_reader :repo_path, :identifier, :changes, :project + + def initialize(repo_path, identifier, changes) + repo_path.gsub!(/\.git\z/, '') + repo_path.gsub!(/\A\//, '') + + @repo_path = repo_path + @identifier = identifier + @changes = deserialize_changes(changes) + + retrieve_project_and_type + end + + def wiki? + @type == :wiki + end + + def regular_project? + @type == :project + end + + def identify(revision) + super(identifier, project, revision) + end + + private + + def retrieve_project_and_type + @type = :project + @project = Project.find_with_namespace(@repo_path) + + if @repo_path.end_with?('.wiki') && !@project + @type = :wiki + @project = Project.find_with_namespace(@repo_path.gsub(/\.wiki\z/, '')) + end + end + + def deserialize_changes(changes) + changes = Base64.decode64(changes) unless changes.include?(' ') + changes = utf8_encode_changes(changes) + changes.lines + end + + def utf8_encode_changes(changes) + changes = changes.dup + + changes.force_encoding('UTF-8') + return changes if changes.valid_encoding? + + # Convert non-UTF-8 branch/tag names to UTF-8 so they can be dumped as JSON. + detection = CharlockHolmes::EncodingDetector.detect(changes) + return changes unless detection && detection[:encoding] + + CharlockHolmes::Converter.convert(changes, detection[:encoding], 'UTF-8') + end + end +end From cde7eee73e611ca7f775e179fb07b931c37f54ae Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Mar 2016 09:24:06 +0200 Subject: [PATCH 059/153] Refactor external users docs [ci skip] --- doc/permissions/permissions.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 2dfd08755b..3d375e47c8 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -74,17 +74,21 @@ Any user can remove themselves from a group, unless they are the last Owner of t ## External Users -In cases where it is desired that a user has access to some internal or private projects, but others -should remain hidden from this user, there is the option of creating `External Users`. -An administrator can flag a user as external through the API or by checking the checkbox on the admin panel. +In cases where it is desired that a user has access only to some internal or +private projects, there is the option of creating **External Users**. This +feature may be useful when for example a contractor is working on a given +project and should only have access to that project. -In the case of a new user: navigate to the **Admin** area and click the **New User** button. If you would like to -edit a user, go to the user list on the **Admin** area and click the **Edit** button. +External users can only access projects to which they are explicitly granted +access, thus hiding all other internal or private ones from them. Access can be +granted by adding the user as member to the project or group. -External users can only access projects to which they are explicitly granted access, thus hiding all internal projects. -Access can be granted by adding the users as member to the project or by including this user in a group. External users will, like usual users, receive -a role in the project or group with all the abilities that are mentioned in the table above. +They will, like usual users, receive a role in the project or group with all +the abilities that are mentioned in the table above. They cannot however create +groups or projects, and they have the same access as logged out users in all +other cases. -External users cannot create groups or projects, and have the same access as logged out users in all other cases. This feature may be -useful when for example a contractor is working on a given project and should only access the given project and public -projects. +An administrator can flag a user as external [through the API](../api/users.md) +or by checking the checkbox on the admin panel. As an administrator, navigate +to **Admin > Users** to create a new user or edit an existing one. There, you +will find the option to flag the user as external. From 51300d9b2a65a43742985847376150229b91c4ca Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Thu, 17 Mar 2016 09:36:00 +0100 Subject: [PATCH 060/153] API support for setting External flag on existing users --- lib/api/users.rb | 3 ++- spec/requests/api/users_spec.rb | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/api/users.rb b/lib/api/users.rb index c574f042a6..8849fff60a 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -108,12 +108,13 @@ module API # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false + # external - Is user an external user - true or false(default) # Example Request: # PUT /users/:id put ":id" do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin, :external] user = User.find(params[:id]) not_found!('User') unless user diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 5366a7bd06..679227bf88 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -282,6 +282,13 @@ describe API::API, api: true do expect(user.reload.admin).to eq(true) end + it "should update external status" do + put api("/users/#{user.id}", admin), { external: true } + expect(response.status).to eq 200 + expect(json_response['external']).to eq(true) + expect(user.reload.external?).to be_truthy + end + it "should not update admin status" do put api("/users/#{admin_user.id}", admin), { can_create_group: false } expect(response.status).to eq(200) From 81439c9093707d8964258db9481715b3ca0e7a3a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 Mar 2016 09:13:20 +0000 Subject: [PATCH 061/153] Moved code to single line if --- app/helpers/issuables_helper.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 23f389b8c5..81df209439 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -25,11 +25,9 @@ module IssuablesHelper if @project member = @project.team.find_member(user_id) - if member - user = member.user - end + user = member.user if member else - user = User.find(user_id) + user = User.find_by(id: user_id) end if user From 956e914307029dbfbdb387fd6c0749dd50935fa4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Mar 2016 12:21:12 +0200 Subject: [PATCH 062/153] Add missing API docs on external user [ci skip] --- doc/api/users.md | 1 + lib/api/users.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 44a29da5ec..383e7c76ab 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -220,6 +220,7 @@ Parameters: - `bio` - User's biography - `admin` (optional) - User is admin - true or false (default) - `can_create_group` (optional) - User can create groups - true or false +- `external` (optional) - Flags the user as external - true or false(default) Note, at the moment this method does only return a 404 error, even in cases where a 409 (Conflict) would be more appropriate, diff --git a/lib/api/users.rb b/lib/api/users.rb index 8849fff60a..13ab17c690 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -61,7 +61,7 @@ module API # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false # confirm - Require user confirmation - true (default) or false - # external - Is user an external user - true or false(default) + # external - Flags the user as external - true or false(default) # Example Request: # POST /users post do @@ -108,7 +108,7 @@ module API # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false - # external - Is user an external user - true or false(default) + # external - Flags the user as external - true or false(default) # Example Request: # PUT /users/:id put ":id" do From feb538dddd5e2e6b09860d34b4a17c3e46a006c8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 Mar 2016 10:21:48 +0000 Subject: [PATCH 063/153] Fixed filter spacing Closes #14350 --- app/assets/stylesheets/framework/filters.scss | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index c431e2b0df..40a508c1eb 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -3,22 +3,11 @@ vertical-align: top; } -@media (min-width: 800px) { +@media (min-width: $screen-sm-min) { .issues-filters, .issues_bulk_update { - select, .select2-container { - width: 120px !important; - display: inline-block; - } - } -} - -@media (min-width: 1200px) { - .issues-filters, - .issues_bulk_update { - select, .select2-container { - width: 150px !important; - display: inline-block; + .dropdown-menu-toggle { + width: 132px; } } } From 9337406671755ebd9175866cd86f1d6da4265d49 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Thu, 17 Mar 2016 11:48:07 +0100 Subject: [PATCH 064/153] Fix specs Spinach was right, I was a fool.. --- app/models/issue.rb | 1 + spec/features/issues/new_branch_button_spec.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/issue.rb b/app/models/issue.rb index 781298a63b..2447f860c5 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -98,6 +98,7 @@ class Issue < ActiveRecord::Base end def related_branches + return [] if self.project.empty_repo? self.project.repository.branch_names.select do |branch| branch =~ /\A#{iid}-(?!\d+-stable)/i end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index 462cb1f8f6..1f3bd915f4 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -24,7 +24,7 @@ feature 'Start new branch from an issue', feature: true do end let(:referenced_mr) do create(:merge_request, :simple, source_project: project, target_project: project, - description: "Fixes ##{issue.iid}") + description: "Fixes ##{issue.iid}") end before do From 019fed0f72116a73f0d410fe0c892fc9a19921e7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 17 Mar 2016 12:16:34 +0100 Subject: [PATCH 065/153] Reuse `no_cache_headers` method in badges controller --- app/controllers/projects/badges_controller.rb | 13 +------------ features/steps/project/badges/build.rb | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index dc9c96df00..6ff47c4033 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -1,5 +1,5 @@ class Projects::BadgesController < Projects::ApplicationController - before_action :set_no_cache + before_action :no_cache_headers def build respond_to do |format| @@ -10,15 +10,4 @@ class Projects::BadgesController < Projects::ApplicationController end end end - - private - - def set_no_cache - expires_now - - # Add some deprecated headers for older agents - # - response.headers['Pragma'] = 'no-cache' - response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' - end end diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb index 47540f356e..66a48a176e 100644 --- a/features/steps/project/badges/build.rb +++ b/features/steps/project/badges/build.rb @@ -21,7 +21,7 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps end step 'I should see a badge that has not been cached' do - expect(page.response_headers).to include('Cache-Control' => 'no-cache') + expect(page.response_headers['Cache-Control']).to include 'no-cache' end def expect_badge(status) From fa570525dbe35fb598b8a269198e1173da6d7ae3 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Tue, 23 Feb 2016 15:09:35 -0500 Subject: [PATCH 066/153] Adds small AJAX optimistic functionality to todos. Fixes #13656 A good first step and boring solution. --- app/assets/javascripts/dispatcher.js.coffee | 3 +- app/assets/javascripts/todos.js.coffee | 48 +++++++++++++++++++ app/assets/stylesheets/framework/flash.scss | 6 +++ app/controllers/dashboard/todos_controller.rb | 7 ++- app/views/dashboard/todos/_todo.html.haml | 2 +- app/views/dashboard/todos/index.html.haml | 6 ++- 6 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/todos.js.coffee diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 1be86e3b82..f5e1ca9860 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -14,7 +14,6 @@ class Dispatcher path = page.split(':') shortcut_handler = null - switch page when 'projects:issues:index' Issues.init() @@ -25,6 +24,8 @@ class Dispatcher new ZenMode() when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show' new Milestone() + when 'dashboard:todos:index' + new Todos() when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() new DropzoneInput($('.milestone-form')) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee new file mode 100644 index 0000000000..b68c143b4b --- /dev/null +++ b/app/assets/javascripts/todos.js.coffee @@ -0,0 +1,48 @@ +class @Todos + _this = null; + constructor: (@name) -> + _this = @ + @initBtnListeners() + + initBtnListeners: -> + $('.done-todo').on('click', @doneClicked) + + doneClicked: (e) -> + $this = $(this) + doneURL = $this.attr('href') + e.preventDefault() + e.stopImmediatePropagation() + $spinner = $('').addClass('fa fa-spinner fa-spin') + $this.addClass("disabled") + $this.append($spinner) + $.ajax + type: 'POST' + url: doneURL + dataType: 'json' + data: '_method': 'delete' + error: (data, textStatus, jqXHR) -> + new Flash('Unable to update your todos.', 'alert') + _this.clearDone($this.closest('li')) + return + + success: (data, textStatus, jqXHR) -> + new Flash(data.notice, 'success') + _this.clearDone($this.closest('li')) + return + + clearDone: ($row) -> + $ul = $row.closest('ul') + $row.remove() + if not $ul.find('li').length + Turbolinks.visit(location.href) + else + $pendingBadge = $('.todos-pending .badge') + $pendingBadge.text parseInt($pendingBadge.text()) - 1 + + $doneBadge = $('.todos-done .badge') + $doneBadge.text parseInt($doneBadge.text()) + 1 + + $mainTodosPendingBadge = $('.todos-pending-count') + $mainTodosPendingBadge.text parseInt($mainTodosPendingBadge.text()) - 1 + return + \ No newline at end of file diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 1bfd021399..244d0e2f0c 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -5,6 +5,12 @@ width: 100%; z-index: 100; + .flash-success { + @extend .alert; + @extend .alert-success; + margin: 0; + } + .flash-notice { @extend .alert; @extend .alert-info; diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 43cf8fa71a..54f718d011 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -8,9 +8,14 @@ class Dashboard::TodosController < Dashboard::ApplicationController def destroy todo.done! + todo_notice = 'Todo was successfully marked as done.' + respond_to do |format| - format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' } + format.html { redirect_to dashboard_todos_path, notice: todo_notice } format.js { render nothing: true } + format.json do + render json: { status: 'OK', notice: todo_notice } + end end end diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 45cfe3da18..b7deb9da54 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -16,7 +16,7 @@ - if todo.pending? .todo-actions.pull-right - = link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn' + = link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn done-todo' .todo-body .todo-note diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 946d7df393..b5b8fb4d14 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -3,13 +3,15 @@ .top-area %ul.nav-links - %li{class: ('active' if params[:state].blank? || params[:state] == 'pending')} + - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending') + %li{class: "todos-pending #{todo_pending_active}"} = link_to todos_filter_path(state: 'pending') do %span To do %span{class: 'badge'} = todos_pending_count - %li{class: ('active' if params[:state] == 'done')} + - todo_done_active = ('active' if params[:state] == 'done') + %li{class: "todos-done #{todo_done_active}"} = link_to todos_filter_path(state: 'done') do %span Done From 8f8720209f1f325042cbe3f134797c85cc0c64c4 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Fri, 4 Mar 2016 09:15:48 -0500 Subject: [PATCH 067/153] Remove `Flash` from todos when finished --- app/assets/javascripts/todos.js.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index b68c143b4b..811652c8d8 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -26,7 +26,6 @@ class @Todos return success: (data, textStatus, jqXHR) -> - new Flash(data.notice, 'success') _this.clearDone($this.closest('li')) return From 421215e3385860af42530895c22021c0704275e2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 Mar 2016 12:54:02 +0000 Subject: [PATCH 068/153] Removed the flash success message Removes the group if empty --- app/assets/javascripts/todos.js.coffee | 52 +++++++------------ app/assets/stylesheets/framework/buttons.scss | 10 ++++ app/assets/stylesheets/framework/flash.scss | 6 --- app/controllers/dashboard/todos_controller.rb | 4 +- app/views/dashboard/todos/_todo.html.haml | 4 +- 5 files changed, 35 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 811652c8d8..951b786b1f 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,47 +1,35 @@ class @Todos - _this = null; constructor: (@name) -> - _this = @ + @clearListeners() @initBtnListeners() - + + clearListeners: -> + $('.done-todo').off('click') + initBtnListeners: -> $('.done-todo').on('click', @doneClicked) - - doneClicked: (e) -> - $this = $(this) - doneURL = $this.attr('href') + + doneClicked: (e) => e.preventDefault() e.stopImmediatePropagation() - $spinner = $('').addClass('fa fa-spinner fa-spin') - $this.addClass("disabled") - $this.append($spinner) + + $this = $(e.currentTarget) + $this.disable() + $.ajax type: 'POST' - url: doneURL + url: $this.attr('href') dataType: 'json' data: '_method': 'delete' - error: (data, textStatus, jqXHR) -> - new Flash('Unable to update your todos.', 'alert') - _this.clearDone($this.closest('li')) - return + success: (data) => + @clearDone $this.closest('li'), data - success: (data, textStatus, jqXHR) -> - _this.clearDone($this.closest('li')) - return - - clearDone: ($row) -> + clearDone: ($row, data) -> $ul = $row.closest('ul') $row.remove() + + $('.todos-pending .badge, .todos-pending-count').text data.count + $('.todos-done .badge').text data.done_count + if not $ul.find('li').length - Turbolinks.visit(location.href) - else - $pendingBadge = $('.todos-pending .badge') - $pendingBadge.text parseInt($pendingBadge.text()) - 1 - - $doneBadge = $('.todos-done .badge') - $doneBadge.text parseInt($doneBadge.text()) + 1 - - $mainTodosPendingBadge = $('.todos-pending-count') - $mainTodosPendingBadge.text parseInt($mainTodosPendingBadge.text()) - 1 - return - \ No newline at end of file + $ul.parents('.panel').remove() diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index fa115a4bf5..657c5f033c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -208,3 +208,13 @@ background-color: #e4e7ed !important; } } + +.btn-loading { + &:not(.disabled) .fa { + display: none; + } + + .fa { + margin-right: 5px; + } +} diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 244d0e2f0c..1bfd021399 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -5,12 +5,6 @@ width: 100%; z-index: 100; - .flash-success { - @extend .alert; - @extend .alert-success; - margin: 0; - } - .flash-notice { @extend .alert; @extend .alert-info; diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 54f718d011..10120dfdf3 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -1,5 +1,5 @@ class Dashboard::TodosController < Dashboard::ApplicationController - before_action :find_todos, only: [:index, :destroy_all] + before_action :find_todos, only: [:index, :destroy, :destroy_all] def index @todos = @todos.page(params[:page]).per(PER_PAGE) @@ -14,7 +14,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController format.html { redirect_to dashboard_todos_path, notice: todo_notice } format.js { render nothing: true } format.json do - render json: { status: 'OK', notice: todo_notice } + render json: { count: @todos.size, done_count: current_user.todos.done.count } end end end diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index b7deb9da54..42f3cf4de0 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -16,7 +16,9 @@ - if todo.pending? .todo-actions.pull-right - = link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn done-todo' + = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do + = icon('spinner spin') + Done .todo-body .todo-note From 41c107beccda574aefd3e5d992345087b2e0d848 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 Mar 2016 13:08:59 +0000 Subject: [PATCH 069/153] Mark all as done through AJAX --- app/assets/javascripts/todos.js.coffee | 31 ++++++++++++++++--- app/controllers/dashboard/todos_controller.rb | 4 +++ app/views/dashboard/todos/_todo.html.haml | 2 +- app/views/dashboard/todos/index.html.haml | 6 ++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 951b786b1f..b6b4bd90e6 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -5,9 +5,11 @@ class @Todos clearListeners: -> $('.done-todo').off('click') + $('.js-todos-mark-all').off('click') initBtnListeners: -> $('.done-todo').on('click', @doneClicked) + $('.js-todos-mark-all').on('click', @allDoneClicked) doneClicked: (e) => e.preventDefault() @@ -22,14 +24,33 @@ class @Todos dataType: 'json' data: '_method': 'delete' success: (data) => - @clearDone $this.closest('li'), data + @clearDone $this.closest('li') + @updateBadges data - clearDone: ($row, data) -> + allDoneClicked: (e) => + e.preventDefault() + e.stopImmediatePropagation() + + $this = $(e.currentTarget) + $this.disable() + + $.ajax + type: 'POST' + url: $this.attr('href') + dataType: 'json' + data: '_method': 'delete' + success: (data) => + $this.remove() + $('.js-todos-list').remove() + @updateBadges data + + clearDone: ($row) -> $ul = $row.closest('ul') $row.remove() - $('.todos-pending .badge, .todos-pending-count').text data.count - $('.todos-done .badge').text data.done_count - if not $ul.find('li').length $ul.parents('.panel').remove() + + updateBadges: (data) -> + $('.todos-pending .badge, .todos-pending-count').text data.count + $('.todos-done .badge').text data.done_count diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 10120dfdf3..7857af9c5d 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -25,6 +25,10 @@ class Dashboard::TodosController < Dashboard::ApplicationController respond_to do |format| format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } format.js { render nothing: true } + format.json do + find_todos + render json: { count: @todos.size, done_count: current_user.todos.done.count } + end end end diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 42f3cf4de0..4c848a5018 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -17,8 +17,8 @@ - if todo.pending? .todo-actions.pull-right = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do - = icon('spinner spin') Done + = icon('spinner spin') .todo-body .todo-note diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index b5b8fb4d14..623381375a 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -20,7 +20,9 @@ .nav-controls - if @todos.any?(&:pending?) - = link_to 'Mark all as done', destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn', method: :delete + = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do + Mark all as done + = icon('spinner spin') .todos-filters .gray-content-block.second-block @@ -44,7 +46,7 @@ .prepend-top-default - if @todos.any? - @todos.group_by(&:project).each do |group| - .panel.panel-default.panel-small + .panel.panel-default.panel-small.js-todos-list - project = group[0] .panel-heading = link_to project.name_with_namespace, namespace_project_path(project.namespace, project) From 4b5e06a944c46043b735952bd02754993de84744 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 17 Mar 2016 09:18:16 -0400 Subject: [PATCH 070/153] Added comment to make builds run --- app/assets/stylesheets/pages/notes.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index f7eeadcb5b..4f9c18b85d 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -108,6 +108,7 @@ ul.notes { word-wrap: break-word; @include md-typography; + // On diffs code should wrap nicely and not overflow pre { code { white-space: pre-wrap; From 4117c815df73ba80258ca472470735826c68753a Mon Sep 17 00:00:00 2001 From: Geoffrey Lalonde Date: Thu, 17 Mar 2016 07:54:56 -0700 Subject: [PATCH 071/153] adjusted behavior so canceled builds tagged as allowed to fail do not fail build --- app/models/commit_status.rb | 2 +- spec/lib/ci/status_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3b1aa0f5c8..3377a85a55 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -114,7 +114,7 @@ class CommitStatus < ActiveRecord::Base end def ignored? - failed? && allow_failure? + allow_failure? && (failed? || canceled?) end def duration diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb index 1539720bb8..47f3df6e3c 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/lib/ci/status_spec.rb @@ -48,6 +48,29 @@ describe Ci::Status do it { is_expected.to eq 'success' } end + context 'success and canceled' do + let(:statuses) do + [create(type, status: :success), create(type, status: :canceled)] + end + it { is_expected.to eq 'failed' } + end + + context 'all canceled' do + let(:statuses) do + [create(type, status: :canceled), create(type, status: :canceled)] + end + it { is_expected.to eq 'canceled' } + end + + context 'success and canceled but allowed to fail' do + let(:statuses) do + [create(type, status: :success), + create(type, status: :canceled, allow_failure: true)] + end + + it { is_expected.to eq 'success' } + end + context 'one finished and second running but allowed to fail' do let(:statuses) do [create(type, status: :success), From 573c8d561f369e8ceac21787a66df42d214bbecf Mon Sep 17 00:00:00 2001 From: Geoffrey Lalonde Date: Thu, 17 Mar 2016 07:46:00 -0700 Subject: [PATCH 072/153] added changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index fcf659c07f..ff417e1a3f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.6.0 (unreleased) - Move group activity to separate page - Continue parameters are checked to ensure redirection goes to the same instance - User deletion is now done in the background so the request can not time out + - Canceled builds are now ignored in compound build status if marked as `allowed to fail` v 8.5.7 - Bump Git version requirement to 2.7.3 From ceaa6943919041c204ec86ebea0347374dffdd44 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 Mar 2016 15:10:45 +0000 Subject: [PATCH 073/153] Fixed failing tests --- features/steps/dashboard/todos.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb index 9722a5a848..963e4f2136 100644 --- a/features/steps/dashboard/todos.rb +++ b/features/steps/dashboard/todos.rb @@ -41,7 +41,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps click_link 'Done' end - expect(page).to have_content 'Todo was successfully marked as done.' expect(page).to have_content 'To do 3' expect(page).to have_content 'Done 1' should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}" From 21952cc7f13c6bf3eaf7c8515d59b79dd2fc6188 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 17 Mar 2016 17:29:09 +0100 Subject: [PATCH 074/153] Add side padding to content-list if inside panel Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index bfec0911b3..2b4bb1eebf 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -141,6 +141,10 @@ ul.content-list { } } +.panel > .content-list > li { + padding: $gl-padding-top $gl-padding; +} + ul.controls { padding-top: 1px; float: right; From 5ae95b1880f99bf0414dccacc51b624c00d6b305 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 17 Mar 2016 17:59:58 +0100 Subject: [PATCH 075/153] Add space to separate different files in diff Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/diff.scss | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index db06b8288c..d5862a11ac 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1,7 +1,7 @@ // Common .diff-file { border: 1px solid $border-color; - border-top: none; + margin-bottom: $gl-padding; .diff-header { position: relative; @@ -361,3 +361,11 @@ border-color: $border; } } + +.files { + margin-top: -1px; + + .diff-file:last-child { + margin-bottom: 0; + } +} From 028c751040b6acc29f3127d5f2d119b899a85205 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 17 Mar 2016 12:20:24 -0500 Subject: [PATCH 076/153] Revert link color from blue --- app/assets/stylesheets/framework/blocks.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 90c3ce0e84..c36f29dda0 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -28,10 +28,6 @@ border-bottom: 1px solid $border-color; color: $gl-gray; - a { - color: $md-link-color; - } - &.oneline-block { line-height: 42px; } From 6048910aeef723faa535ce7ba6bb88a5e3360df9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 17 Mar 2016 13:38:35 -0400 Subject: [PATCH 077/153] Prevent a 500 when a repository's root_ref is nil Closes #14259 --- app/views/layouts/header/_default.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 77d01a7736..f3090b9670 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -46,6 +46,8 @@ %h1.title= title = render 'shared/outdated_browser' + - if @project && !@project.empty_repo? - :javascript - var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}"; + - if ref = @ref || @project.repository.root_ref + :javascript + var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}"; From cd05d3f78d2093baab39f6c3c114d1ab7138309f Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 17 Mar 2016 16:53:05 +0100 Subject: [PATCH 078/153] Cache project avatars stored in Git The avatar logic has been moved from Project to Repository as this makes caching easier. The logic itself in turn has been changed so that the logo file names are cached in Redis. This cache is flushed upon pushing a commit but _only_ if: 1. The commit was pushed to the default branch 2. The commit actually changes any of the logo files If no branch or commit is given the cache is flushed anyway, this ensures that calling Repository#expire_cache without any arguments still flushes the avatar cache (e.g. this is used when removing a project). Fixes gitlab-org/gitlab-ce#14363 --- app/models/project.rb | 5 +- app/models/repository.rb | 36 +++++++++++- app/services/git_push_service.rb | 2 +- spec/models/repository_spec.rb | 81 +++++++++++++++++++++++++- spec/services/git_push_service_spec.rb | 9 ++- 5 files changed, 120 insertions(+), 13 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index ab4913e99a..412c6c6732 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -571,10 +571,7 @@ class Project < ActiveRecord::Base end def avatar_in_git - @avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png') - @avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg') - @avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif') - @avatar_file + repository.avatar end def avatar_url diff --git a/app/models/repository.rb b/app/models/repository.rb index e555e97689..036919c27b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -3,6 +3,10 @@ require 'securerandom' class Repository class CommitError < StandardError; end + # Files to use as a project avatar in case no avatar was uploaded via the web + # UI. + AVATAR_FILES = %w{logo.png logo.jpg logo.gif} + include Gitlab::ShellAdapter attr_accessor :path_with_namespace, :project @@ -241,12 +245,13 @@ class Repository @branches = nil end - def expire_cache(branch_name = nil) + def expire_cache(branch_name = nil, revision = nil) cache_keys.each do |key| cache.expire(key) end expire_branch_cache(branch_name) + expire_avatar_cache(branch_name, revision) # This ensures this particular cache is flushed after the first commit to a # new repository. @@ -316,6 +321,23 @@ class Repository cache.expire(:branch_names) end + def expire_avatar_cache(branch_name = nil, revision = nil) + # Avatars are pulled from the default branch, thus if somebody pushes to a + # different branch there's no need to expire anything. + return if branch_name && branch_name != root_ref + + # We don't want to flush the cache if the commit didn't actually make any + # changes to any of the possible avatar files. + if revision && commit = self.commit(revision) + return unless commit.diffs. + any? { |diff| AVATAR_FILES.include?(diff.new_path) } + end + + cache.expire(:avatar) + + @avatar = nil + end + # Runs code just before a repository is deleted. def before_delete expire_cache if exists? @@ -350,8 +372,8 @@ class Repository end # Runs code after a new commit has been pushed. - def after_push_commit(branch_name) - expire_cache(branch_name) + def after_push_commit(branch_name, revision) + expire_cache(branch_name, revision) end # Runs code after a new branch has been created. @@ -857,6 +879,14 @@ class Repository end end + def avatar + @avatar ||= cache.fetch(:avatar) do + AVATAR_FILES.find do |file| + blob_at_branch('master', file) + end + end + end + private def cache diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index d840ab5e34..14e2a2c069 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -17,7 +17,7 @@ class GitPushService < BaseService # 6. Checks if the project's main language has changed # def execute - @project.repository.after_push_commit(branch_name) + @project.repository.after_push_commit(branch_name, params[:newrev]) if push_remove_branch? @project.repository.after_remove_branch diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index fc2ab2d993..536fe66b21 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -597,9 +597,9 @@ describe Repository, models: true do describe '#after_push_commit' do it 'flushes the cache' do - expect(repository).to receive(:expire_cache).with('master') + expect(repository).to receive(:expire_cache).with('master', '123') - repository.after_push_commit('master') + repository.after_push_commit('master', '123') end end @@ -703,4 +703,81 @@ describe Repository, models: true do repository.rm_tag('8.5') end end + + describe '#avatar' do + it 'returns the first avatar file found in the repository' do + expect(repository).to receive(:blob_at_branch). + with('master', 'logo.png'). + and_return(true) + + expect(repository.avatar).to eq('logo.png') + end + + it 'caches the output' do + allow(repository).to receive(:blob_at_branch). + with('master', 'logo.png'). + and_return(true) + + expect(repository.avatar).to eq('logo.png') + + expect(repository).to_not receive(:blob_at_branch) + expect(repository.avatar).to eq('logo.png') + end + end + + describe '#expire_avatar_cache' do + let(:cache) { repository.send(:cache) } + + before do + allow(repository).to receive(:cache).and_return(cache) + end + + context 'without a branch or revision' do + it 'flushes the cache' do + expect(cache).to receive(:expire).with(:avatar) + + repository.expire_avatar_cache + end + end + + context 'with a branch' do + it 'does not flush the cache if the branch is not the default branch' do + expect(cache).not_to receive(:expire) + + repository.expire_avatar_cache('cats') + end + + it 'flushes the cache if the branch equals the default branch' do + expect(cache).to receive(:expire).with(:avatar) + + repository.expire_avatar_cache(repository.root_ref) + end + end + + context 'with a branch and revision' do + let(:commit) { double(:commit) } + + before do + allow(repository).to receive(:commit).and_return(commit) + end + + it 'does not flush the cache if the commit does not change any logos' do + diff = double(:diff, new_path: 'test.txt') + + expect(commit).to receive(:diffs).and_return([diff]) + expect(cache).not_to receive(:expire) + + repository.expire_avatar_cache(repository.root_ref, '123') + end + + it 'flushes the cache if the commit changes any of the logos' do + diff = double(:diff, new_path: Repository::AVATAR_FILES[0]) + + expect(commit).to receive(:diffs).and_return([diff]) + expect(cache).to receive(:expire).with(:avatar) + + repository.expire_avatar_cache(repository.root_ref, '123') + end + end + end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 145bc93756..b49ca96e8e 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -29,7 +29,8 @@ describe GitPushService, services: true do it { is_expected.to be_truthy } it 'flushes general cached data' do - expect(project.repository).to receive(:expire_cache).with('master') + expect(project.repository).to receive(:expire_cache). + with('master', newrev) subject end @@ -46,7 +47,8 @@ describe GitPushService, services: true do it { is_expected.to be_truthy } it 'flushes general cached data' do - expect(project.repository).to receive(:expire_cache).with('master') + expect(project.repository).to receive(:expire_cache). + with('master', newrev) subject end @@ -65,7 +67,8 @@ describe GitPushService, services: true do end it 'flushes general cached data' do - expect(project.repository).to receive(:expire_cache).with('master') + expect(project.repository).to receive(:expire_cache). + with('master', newrev) subject end From cfb10c103f1298b7c4ec94c76f66445a5969d13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Thu, 17 Mar 2016 15:16:34 -0500 Subject: [PATCH 079/153] Check push permissions only when pushing directly to target branch. --- app/services/commits/revert_service.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb index 9cb918d7a2..a3c950ede1 100644 --- a/app/services/commits/revert_service.rb +++ b/app/services/commits/revert_service.rb @@ -9,7 +9,8 @@ module Commits @commit = params[:commit] @create_merge_request = params[:create_merge_request].present? - validate and commit + check_push_permissions unless @create_merge_request + commit rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError, ReversionError => ex error(ex.message) @@ -45,11 +46,11 @@ module Commits end end - def validate + def check_push_permissions allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) unless allowed - raise_error('You are not allowed to push into this branch') + raise ValidationError.new('You are not allowed to push into this branch') end true From 81d191ede9eed2ef63d772a40038679944d8fd69 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 17 Mar 2016 17:50:59 +0100 Subject: [PATCH 080/153] Removed Repository#rebuild_cache This method is not used or tested anywhere. --- app/models/repository.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 036919c27b..53d3d90859 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -301,18 +301,6 @@ class Repository @tag_count = nil end - def rebuild_cache - cache_keys.each do |key| - cache.expire(key) - send(key) - end - - branches.each do |branch| - cache.expire(:"diverging_commit_counts_#{branch.name}") - diverging_commit_counts(branch) - end - end - def lookup_cache @lookup_cache ||= {} end From 3d7feeede32a81df06f80f6f3599bfe62aa6e13d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 17 Mar 2016 17:52:19 +0100 Subject: [PATCH 081/153] Don't rebuild diverging commit count caches When calling Repository#build_cache we _don't_ want to build the diverging commit count cache as doing so can be _very_ slow for repositories with lots of branches. Because these caches are built whenever needed (= when actually viewing the list of branches in the web UI) we can safely remove this code from Repository#build_cache. --- app/models/repository.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 53d3d90859..25d24493f6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -227,12 +227,6 @@ class Repository send(key) end end - - branches.each do |branch| - unless cache.exist?(:"diverging_commit_counts_#{branch.name}") - send(:diverging_commit_counts, branch) - end - end end def expire_tags_cache From dd4b789765ca4219f89c03d14d0c2524b2374184 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 17 Mar 2016 18:15:09 +0100 Subject: [PATCH 082/153] Added tests for Repository#build_cache --- spec/models/repository_spec.rb | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 536fe66b21..a57229a4fd 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -780,4 +780,34 @@ describe Repository, models: true do end end end + + describe '#build_cache' do + let(:cache) { repository.send(:cache) } + + it 'builds the caches if they do not already exist' do + expect(cache).to receive(:exist?). + exactly(repository.cache_keys.length). + times. + and_return(false) + + repository.cache_keys.each do |key| + expect(repository).to receive(key) + end + + repository.build_cache + end + + it 'does not build any caches that already exist' do + expect(cache).to receive(:exist?). + exactly(repository.cache_keys.length). + times. + and_return(true) + + repository.cache_keys.each do |key| + expect(repository).to_not receive(key) + end + + repository.build_cache + end + end end From 515dd63d654dff964615cf0a2fb2c841b6302767 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 17 Mar 2016 22:21:10 +0100 Subject: [PATCH 083/153] Bump Git version requirement to 2.7.4 --- CHANGELOG | 3 +++ README.md | 2 +- doc/install/installation.md | 10 +++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 75659d4766..6c48da07ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,9 @@ v 8.6.0 (unreleased) - User deletion is now done in the background so the request can not time out - Canceled builds are now ignored in compound build status if marked as `allowed to fail` +v 8.5.8 + - Bump Git version requirement to 2.7.4 + v 8.5.7 - Bump Git version requirement to 2.7.3 diff --git a/README.md b/README.md index 208427fcf8..afa60116eb 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ GitLab is a Ruby on Rails application that runs on the following software: - Ubuntu/Debian/CentOS/RHEL - Ruby (MRI) 2.1 -- Git 2.7.3+ +- Git 2.7.4+ - Redis 2.8+ - MySQL or PostgreSQL diff --git a/doc/install/installation.md b/doc/install/installation.md index aa989417c4..c567846f62 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -76,7 +76,7 @@ Make sure you have the right version of Git installed # Install Git sudo apt-get install -y git-core - # Make sure Git is version 2.7.3 or higher + # Make sure Git is version 2.7.4 or higher git --version Is the system packaged Git too old? Remove it and compile from source. @@ -89,9 +89,9 @@ Is the system packaged Git too old? Remove it and compile from source. # Download and compile from source cd /tmp - curl -O --progress https://www.kernel.org/pub/software/scm/git/git-2.7.3.tar.gz - echo '30d067499b61caddedaf1a407b4947244f14d10842d100f7c7c6ea1c288280cd git-2.7.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.7.3.tar.gz - cd git-2.7.3/ + curl -O --progress https://www.kernel.org/pub/software/scm/git/git-2.7.4.tar.gz + echo '7104c4f5d948a75b499a954524cb281fe30c6649d8abe20982936f75ec1f275b git-2.7.4.tar.gz' | shasum -a256 -c - && tar -xzf git-2.7.4.tar.gz + cd git-2.7.4/ ./configure make prefix=/usr/local all @@ -161,7 +161,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Install the database packages sudo apt-get install -y postgresql postgresql-client libpq-dev - + # Create a user for GitLab sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" From 613e0bc5411bd767eeca5439876d61d843957826 Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Thu, 17 Mar 2016 22:34:42 +0100 Subject: [PATCH 084/153] Fix inconsistency in the header of issue and merge request pages --- app/views/projects/issues/show.html.haml | 15 +++++++------ .../merge_requests/show/_mr_title.html.haml | 21 +++++++++++-------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 1e8308277c..494f58c57c 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -25,17 +25,16 @@ %strong.identifier Issue ##{@issue.iid} %span.creator - by + opened .editor-details .editor-details - %strong - = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs") - %span.hidden-xs - = '@' + @issue.author.username - %strong - = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", - by_username: true, avatar: false) = time_ago_with_tooltip(@issue.created_at) + by + %strong + = link_to_member(@project, @issue.author, avatar: false, size: 24, mobile_classes: "hidden-xs") + %strong + = link_to_member(@project, @issue.author, avatar: false, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", + by_username: true, avatar: false) .pull-right.issue-btn-group - if can?(current_user, :create_issue, @project) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index a75c0d96c5..c6cbe8589e 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -8,18 +8,21 @@ = icon('angle-double-left') .issue-meta %strong.identifier - Merge Request ##{@merge_request.iid} + %span.hidden-sm.hidden-md.hidden-lg + MR + %span.hidden-xs + Merge Request + !#{@merge_request.iid} %span.creator - by + opened .editor-details - %strong - = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs") - %span.hidden-xs - = '@' + @merge_request.author.username - %strong - = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", - by_username: true, avatar: false) = time_ago_with_tooltip(@merge_request.created_at) + by + %strong + = link_to_member(@project, @merge_request.author, avatar: false, size: 24, mobile_classes: "hidden-xs") + %strong + = link_to_member(@project, @merge_request.author, avatar: false, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", + by_username: true, avatar: false) .issue-btn-group.pull-right - if can?(current_user, :update_merge_request, @merge_request) From abb77d0f6cec87455e7da13d6a977ac4b8f740f4 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 16:14:54 -0300 Subject: [PATCH 085/153] Add a confidential flag to issues --- db/migrate/20160223192159_add_confidential_to_issues.rb | 6 ++++++ db/schema.rb | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160223192159_add_confidential_to_issues.rb diff --git a/db/migrate/20160223192159_add_confidential_to_issues.rb b/db/migrate/20160223192159_add_confidential_to_issues.rb new file mode 100644 index 0000000000..e9d47fd589 --- /dev/null +++ b/db/migrate/20160223192159_add_confidential_to_issues.rb @@ -0,0 +1,6 @@ +class AddConfidentialToIssues < ActiveRecord::Migration + def change + add_column :issues, :confidential, :boolean, default: false + add_index :issues, :confidential + end +end diff --git a/db/schema.rb b/db/schema.rb index 2f075677b3..7e6863ef47 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160316123110) do +ActiveRecord::Schema.define(version: 20160316204731) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -416,10 +416,12 @@ ActiveRecord::Schema.define(version: 20160316123110) do t.string "state" t.integer "iid" t.integer "updated_by_id" + t.boolean "confidential", default: false end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree + add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} From 6b86d3fb800bb551af4a446b87dfd64c963733a3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 16:16:48 -0300 Subject: [PATCH 086/153] Add an option to user make an issue confidential --- app/controllers/projects/issues_controller.rb | 2 +- app/views/shared/issuable/_form.html.haml | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index aa7a178dcf..0907733fe4 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -158,7 +158,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue_params params.require(:issue).permit( - :title, :assignee_id, :position, :description, + :title, :assignee_id, :position, :description, :confidential, :milestone_id, :state_event, :task_num, label_ids: [] ) end diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index d5a4aad05d..9ef729e960 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -29,6 +29,15 @@ = render 'projects/notes/hints' .clearfix .error-alert + +- if issuable.is_a?(Issue) && !issuable.project.private? + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :confidential do + = f.check_box :confidential + This issue is confidential and should only be visible to team members + - if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) %hr .form-group From 34ee75379cf8e6459b8926fbf956a8316f87eea7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 16:38:51 -0300 Subject: [PATCH 087/153] Restrict access to confidential issues --- app/controllers/projects/issues_controller.rb | 6 +- app/finders/issues_finder.rb | 6 + app/models/ability.rb | 17 +- app/models/issue.rb | 7 + .../projects/issues_controller_spec.rb | 164 +++++++++++++++++- spec/factories/issues.rb | 4 + 6 files changed, 193 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 0907733fe4..6603f28a08 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -5,7 +5,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action :issue, only: [:edit, :update, :show] # Allow read any issue - before_action :authorize_read_issue! + before_action :authorize_read_issue!, only: [:show] # Allow write(create) issue before_action :authorize_create_issue!, only: [:new, :create] @@ -128,6 +128,10 @@ class Projects::IssuesController < Projects::ApplicationController end alias_method :subscribable_resource, :issue + def authorize_read_issue! + return render_404 unless can?(current_user, :read_issue, @issue) + end + def authorize_update_issue! return render_404 unless can?(current_user, :update_issue, @issue) end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 20a2b0ce8f..c2befa5a5b 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder def klass Issue end + + private + + def init_collection + Issue.visible_to_user(current_user) + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index ccac08b7d3..e22da4806e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -49,7 +49,6 @@ class Ability rules = [ :read_project, :read_wiki, - :read_issue, :read_label, :read_milestone, :read_project_snippet, @@ -63,6 +62,9 @@ class Ability # Allow to read builds by anonymous user if guests are allowed rules << :read_build if project.public_builds? + # Allow to read issues by anonymous user if issue is not confidential + rules << :read_issue unless subject.is_a?(Issue) && subject.confidential? + rules - project_disabled_features_rules(project) else [] @@ -321,6 +323,7 @@ class Ability end rules += project_abilities(user, subject.project) + rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue) rules end end @@ -439,5 +442,17 @@ class Ability :"admin_#{name}" ] end + + def filter_confidential_issues_abilities(user, issue, rules) + return rules if user.admin? || !issue.confidential? + + unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id) + rules.delete(:admin_issue) + rules.delete(:read_issue) + rules.delete(:update_issue) + end + + rules + end end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 2447f860c5..053387cffd 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -58,6 +58,13 @@ class Issue < ActiveRecord::Base attributes end + def self.visible_to_user(user) + return where(confidential: false) if user.blank? + return all if user.admin? + + where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id)) + end + def self.reference_prefix '#' end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 76d56bc989..2cd8123114 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1,16 +1,16 @@ require('spec_helper') describe Projects::IssuesController do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:issue) { create(:issue, project: project) } - - before do - sign_in(user) - project.team << [user, :developer] - end - describe "GET #index" do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project) } + + before do + sign_in(user) + project.team << [user, :developer] + end + it "returns index" do get :index, namespace_id: project.namespace.path, project_id: project.path @@ -38,6 +38,152 @@ describe Projects::IssuesController do get :index, namespace_id: project.namespace.path, project_id: project.path expect(response.status).to eq(404) end + end + describe 'Confidential Issues' do + let(:project) { create(:empty_project, :public) } + let(:assignee) { create(:assignee) } + let(:author) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:admin) { create(:admin) } + let!(:issue) { create(:issue, project: project) } + let!(:unescaped_parameter_value) { create(:issue, :confidential, project: project, author: author) } + let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignee: assignee) } + + describe 'GET #index' do + it 'should not list confidential issues for guests' do + sign_out(:user) + get_issues + + expect(assigns(:issues)).to eq [issue] + end + + it 'should not list confidential issues for non project members' do + sign_in(non_member) + get_issues + + expect(assigns(:issues)).to eq [issue] + end + + it 'should list confidential issues for author' do + sign_in(author) + get_issues + + expect(assigns(:issues)).to include unescaped_parameter_value + expect(assigns(:issues)).not_to include request_forgery_timing_attack + end + + it 'should list confidential issues for assignee' do + sign_in(assignee) + get_issues + + expect(assigns(:issues)).not_to include unescaped_parameter_value + expect(assigns(:issues)).to include request_forgery_timing_attack + end + + it 'should list confidential issues for project members' do + sign_in(member) + project.team << [member, :developer] + + get_issues + + expect(assigns(:issues)).to include unescaped_parameter_value + expect(assigns(:issues)).to include request_forgery_timing_attack + end + + it 'should list confidential issues for admin' do + sign_in(admin) + get_issues + + expect(assigns(:issues)).to include unescaped_parameter_value + expect(assigns(:issues)).to include request_forgery_timing_attack + end + + def get_issues + get :index, + namespace_id: project.namespace.to_param, + project_id: project.to_param + end + end + + shared_examples_for 'restricted action' do |http_status| + it 'returns 404 for guests' do + sign_out :user + go(id: unescaped_parameter_value.to_param) + + expect(response).to have_http_status :not_found + end + + it 'returns 404 for non project members' do + sign_in(non_member) + go(id: unescaped_parameter_value.to_param) + + expect(response).to have_http_status :not_found + end + + it "returns #{http_status[:success]} for author" do + sign_in(author) + go(id: unescaped_parameter_value.to_param) + + expect(response).to have_http_status http_status[:success] + end + + it "returns #{http_status[:success]} for assignee" do + sign_in(assignee) + go(id: request_forgery_timing_attack.to_param) + + expect(response).to have_http_status http_status[:success] + end + + it "returns #{http_status[:success]} for project members" do + sign_in(member) + project.team << [member, :developer] + go(id: unescaped_parameter_value.to_param) + + expect(response).to have_http_status http_status[:success] + end + + it "returns #{http_status[:success]} for admin" do + sign_in(admin) + go(id: unescaped_parameter_value.to_param) + + expect(response).to have_http_status http_status[:success] + end + end + + describe 'GET #show' do + it_behaves_like 'restricted action', success: 200 + + def go(id:) + get :show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id + end + end + + describe 'GET #edit' do + it_behaves_like 'restricted action', success: 200 + + def go(id:) + get :edit, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id + end + end + + describe 'PUT #update' do + it_behaves_like 'restricted action', success: 302 + + def go(id:) + put :update, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id, + issue: { title: 'New title' } + end + end end end diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 722095de59..e72aa9479b 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -4,6 +4,10 @@ FactoryGirl.define do author project + trait :confidential do + confidential true + end + trait :closed do state :closed end From 43d8bdb4f048cbeb5675ed9120cb1aeb415b9586 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 17:39:50 -0300 Subject: [PATCH 088/153] Restrict access to references for confidential issues --- lib/banzai/filter/issue_reference_filter.rb | 5 ++ .../features/issues/new_branch_button_spec.rb | 2 +- .../lib/banzai/filter/redactor_filter_spec.rb | 72 ++++++++++++++++++- .../gitlab/closing_issue_extractor_spec.rb | 1 + spec/lib/gitlab/reference_extractor_spec.rb | 2 + spec/models/commit_spec.rb | 13 +++- spec/models/concerns/mentionable_spec.rb | 5 +- spec/models/merge_request_spec.rb | 1 + spec/services/git_push_service_spec.rb | 4 ++ spec/support/mentionable_shared_examples.rb | 2 + 10 files changed, 102 insertions(+), 5 deletions(-) diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 9f08aa36e8..2732e0b514 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -9,6 +9,11 @@ module Banzai Issue end + def self.user_can_see_reference?(user, node, context) + issue = Issue.find(node.attr('data-issue')) rescue nil + Ability.abilities.allowed?(user, :read_issue, issue) + end + def find_object(project, id) project.get_issue(id) end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index 1f3bd915f4..9219b76754 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -24,7 +24,7 @@ feature 'Start new branch from an issue', feature: true do end let(:referenced_mr) do create(:merge_request, :simple, source_project: project, target_project: project, - description: "Fixes ##{issue.iid}") + description: "Fixes ##{issue.iid}", author: user) end before do diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb index e9bb388e36..9acf6304bc 100644 --- a/spec/lib/banzai/filter/redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/redactor_filter_spec.rb @@ -44,8 +44,78 @@ describe Banzai::Filter::RedactorFilter, lib: true do end end - context "for user references" do + context 'with data-issue' do + context 'for confidential issues' do + it 'removes references for non project members' do + non_member = create(:user) + project = create(:empty_project, :public) + issue = create(:issue, :confidential, project: project) + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + doc = filter(link, current_user: non_member) + + expect(doc.css('a').length).to eq 0 + end + + it 'allows references for author' do + author = create(:user) + project = create(:empty_project, :public) + issue = create(:issue, :confidential, project: project, author: author) + + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + doc = filter(link, current_user: author) + + expect(doc.css('a').length).to eq 1 + end + + it 'allows references for assignee' do + assignee = create(:user) + project = create(:empty_project, :public) + issue = create(:issue, :confidential, project: project, assignee: assignee) + + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + doc = filter(link, current_user: assignee) + + expect(doc.css('a').length).to eq 1 + end + + it 'allows references for project members' do + member = create(:user) + project = create(:empty_project, :public) + project.team << [member, :developer] + issue = create(:issue, :confidential, project: project) + + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + doc = filter(link, current_user: member) + + expect(doc.css('a').length).to eq 1 + end + + it 'allows references for admin' do + admin = create(:admin) + project = create(:empty_project, :public) + issue = create(:issue, :confidential, project: project) + + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + doc = filter(link, current_user: admin) + + expect(doc.css('a').length).to eq 1 + end + end + + it 'allows references for non confidential issues' do + user = create(:user) + project = create(:empty_project, :public) + issue = create(:issue, project: project) + + link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter') + doc = filter(link, current_user: user) + + expect(doc.css('a').length).to eq 1 + end + end + + context "for user references" do context 'with data-group' do it 'removes unpermitted Group references' do user = create(:user) diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 04cf11fc6f..844fd79c99 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -11,6 +11,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do subject { described_class.new(project, project.creator) } before do + project.team << [project.creator, :developer] project2.team << [project.creator, :master] end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 7d963795e1..65af37e24f 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Gitlab::ReferenceExtractor, lib: true do let(:project) { create(:project) } + subject { Gitlab::ReferenceExtractor.new(project, project.creator) } it 'accesses valid user objects' do @@ -41,6 +42,7 @@ describe Gitlab::ReferenceExtractor, lib: true do end it 'accesses valid issue objects' do + project.team << [project.creator, :developer] @i0 = create(:issue, project: project) @i1 = create(:issue, project: project) diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 253902512c..0e9111c802 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -86,10 +86,21 @@ eos let(:issue) { create :issue, project: project } let(:other_project) { create :project, :public } let(:other_issue) { create :issue, project: other_project } + let(:commiter) { create :user } + + before do + project.team << [commiter, :developer] + other_project.team << [commiter, :developer] + end it 'detects issues that this commit is marked as closing' do ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}" - allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}") + + allow(commit).to receive_messages( + safe_message: "Fixes ##{issue.iid} and #{ext_ref}", + committer_email: commiter.email + ) + expect(commit.closes_issues).to include(issue) expect(commit.closes_issues).to include(other_issue) end diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 20f0c561e4..cb33edde82 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -48,7 +48,8 @@ describe Issue, "Mentionable" do describe '#create_new_cross_references!' do let(:project) { create(:project) } - let(:issues) { create_list(:issue, 2, project: project) } + let(:author) { create(:author) } + let(:issues) { create_list(:issue, 2, project: project, author: author) } context 'before changes are persisted' do it 'ignores pre-existing references' do @@ -91,7 +92,7 @@ describe Issue, "Mentionable" do end def create_issue(description:) - create(:issue, project: project, description: description) + create(:issue, project: project, description: description, author: author) end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 8bf68013fd..633a16b59c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -150,6 +150,7 @@ describe MergeRequest, models: true do let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") } before do + subject.project.team << [subject.author, :developer] allow(subject).to receive(:commits).and_return([commit0, commit1, commit2]) end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index b49ca96e8e..8490a729e5 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -215,12 +215,16 @@ describe GitPushService, services: true do let(:commit) { project.commit } before do + project.team << [commit_author, :developer] + project.team << [user, :developer] + allow(commit).to receive_messages( safe_message: "this commit \n mentions #{issue.to_reference}", references: [issue], author_name: commit_author.name, author_email: commit_author.email ) + allow(project.repository).to receive(:commits_between).and_return([commit]) end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index fce91015fd..e876d44c16 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -52,6 +52,8 @@ shared_context 'mentionable context' do end set_mentionable_text.call(ref_string) + + project.team << [author, :developer] end end From 7ee528336ad86e478b6db6d0039aec607c8f0192 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 17:39:56 -0300 Subject: [PATCH 089/153] Restrict access for confidential issues on autocomplete --- app/controllers/projects_controller.rb | 2 +- app/services/projects/autocomplete_service.rb | 6 +- .../projects/autocomplete_service_spec.rb | 79 +++++++++++++++++++ 3 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 spec/services/projects/autocomplete_service_spec.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 36f37221c5..c993048077 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -134,7 +134,7 @@ class ProjectsController < ApplicationController def autocomplete_sources note_type = params['type'] note_id = params['type_id'] - autocomplete = ::Projects::AutocompleteService.new(@project) + autocomplete = ::Projects::AutocompleteService.new(@project, current_user) participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id) @suggestions = { diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 7408e09ed1..ba50305dbd 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -1,11 +1,7 @@ module Projects class AutocompleteService < BaseService - def initialize(project) - @project = project - end - def issues - @project.issues.opened.select([:iid, :title]) + @project.issues.visible_to_user(current_user).opened.select([:iid, :title]) end def merge_requests diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb new file mode 100644 index 0000000000..6108c26a78 --- /dev/null +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe Projects::AutocompleteService, services: true do + describe '#issues' do + describe 'confidential issues' do + let(:author) { create(:user) } + let(:assignee) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:admin) { create(:admin) } + let(:project) { create(:empty_project, :public) } + let!(:issue) { create(:issue, project: project, title: 'Issue 1') } + let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) } + let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) } + + it 'should not list project confidential issues for guests' do + autocomplete = described_class.new(project, nil) + issues = autocomplete.issues.map(&:iid) + + expect(issues).to include issue.iid + expect(issues).not_to include security_issue_1.iid + expect(issues).not_to include security_issue_2.iid + expect(issues.count).to eq 1 + end + + it 'should not list project confidential issues for non project members' do + autocomplete = described_class.new(project, non_member) + issues = autocomplete.issues.map(&:iid) + + expect(issues).to include issue.iid + expect(issues).not_to include security_issue_1.iid + expect(issues).not_to include security_issue_2.iid + expect(issues.count).to eq 1 + end + + it 'should list project confidential issues for author' do + autocomplete = described_class.new(project, author) + issues = autocomplete.issues.map(&:iid) + + expect(issues).to include issue.iid + expect(issues).to include security_issue_1.iid + expect(issues).not_to include security_issue_2.iid + expect(issues.count).to eq 2 + end + + it 'should list project confidential issues for assignee' do + autocomplete = described_class.new(project, assignee) + issues = autocomplete.issues.map(&:iid) + + expect(issues).to include issue.iid + expect(issues).not_to include security_issue_1.iid + expect(issues).to include security_issue_2.iid + expect(issues.count).to eq 2 + end + + it 'should list project confidential issues for project members' do + project.team << [member, :developer] + + autocomplete = described_class.new(project, member) + issues = autocomplete.issues.map(&:iid) + + expect(issues).to include issue.iid + expect(issues).to include security_issue_1.iid + expect(issues).to include security_issue_2.iid + expect(issues.count).to eq 3 + end + + it 'should list all project issues for admin' do + autocomplete = described_class.new(project, admin) + issues = autocomplete.issues.map(&:iid) + + expect(issues).to include issue.iid + expect(issues).to include security_issue_1.iid + expect(issues).to include security_issue_2.iid + expect(issues.count).to eq 3 + end + end + end +end From e4f1c001e6886d6001a258bf2fad75f8b424eff1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 17:45:00 -0300 Subject: [PATCH 090/153] Restrict access to confidential issues through API --- lib/api/issues.rb | 3 +- spec/requests/api/issues_spec.rb | 112 ++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 252744515d..fda6f84143 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -82,7 +82,7 @@ module API # GET /projects/:id/issues?milestone=1.0.0&state=closed # GET /issues?iid=42 get ":id/issues" do - issues = user_project.issues + issues = user_project.issues.visible_to_user(current_user) issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil? @@ -104,6 +104,7 @@ module API # GET /projects/:id/issues/:issue_id get ":id/issues/:issue_id" do @issue = user_project.issues.find(params[:issue_id]) + not_found! unless can?(current_user, :read_issue, @issue) present @issue, with: Entities::Issue end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 571ea2dae4..bb2ab05800 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -3,7 +3,11 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace ) } + let(:non_member) { create(:user) } + let(:author) { create(:author) } + let(:assignee) { create(:assignee) } + let(:admin) { create(:admin) } + let!(:project) { create(:project, :public, namespace: user.namespace ) } let!(:closed_issue) do create :closed_issue, author: user, @@ -12,6 +16,13 @@ describe API::API, api: true do state: :closed, milestone: milestone end + let!(:confidential_issue) do + create :issue, + :confidential, + project: project, + author: author, + assignee: assignee + end let!(:issue) do create :issue, author: user, @@ -123,10 +134,43 @@ describe API::API, api: true do let(:base_url) { "/projects/#{project.id}" } let(:title) { milestone.title } - it "should return project issues" do + it 'should return project issues without confidential issues for non project members' do + get api("#{base_url}/issues", non_member) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.first['title']).to eq(issue.title) + end + + it 'should return project confidential issues for author' do + get api("#{base_url}/issues", author) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.first['title']).to eq(issue.title) + end + + it 'should return project confidential issues for assignee' do + get api("#{base_url}/issues", assignee) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.first['title']).to eq(issue.title) + end + + it 'should return project issues with confidential issues for project members' do get api("#{base_url}/issues", user) expect(response.status).to eq(200) expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.first['title']).to eq(issue.title) + end + + it 'should return project confidential issues for admin' do + get api("#{base_url}/issues", admin) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) end @@ -206,6 +250,41 @@ describe API::API, api: true do get api("/projects/#{project.id}/issues/54321", user) expect(response.status).to eq(404) end + + context 'confidential issues' do + it "should return 404 for non project members" do + get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member) + expect(response.status).to eq(404) + end + + it "should return confidential issue for project members" do + get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) + expect(response.status).to eq(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + + it "should return confidential issue for author" do + get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author) + expect(response.status).to eq(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + + it "should return confidential issue for assignee" do + get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee) + expect(response.status).to eq(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + + it "should return confidential issue for admin" do + get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin) + expect(response.status).to eq(200) + expect(json_response['title']).to eq(confidential_issue.title) + expect(json_response['iid']).to eq(confidential_issue.iid) + end + end end describe "POST /projects/:id/issues" do @@ -294,6 +373,35 @@ describe API::API, api: true do expect(response.status).to eq(400) expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end + + context 'confidential issues' do + it "should return 403 for non project members" do + put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member), + title: 'updated title' + expect(response.status).to eq(403) + end + + it "should update a confidential issue for project members" do + put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), + title: 'updated title' + expect(response.status).to eq(200) + expect(json_response['title']).to eq('updated title') + end + + it "should update a confidential issue for author" do + put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author), + title: 'updated title' + expect(response.status).to eq(200) + expect(json_response['title']).to eq('updated title') + end + + it "should update a confidential issue for admin" do + put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin), + title: 'updated title' + expect(response.status).to eq(200) + expect(json_response['title']).to eq('updated title') + end + end end describe 'PUT /projects/:id/issues/:issue_id to update labels' do From f2ba4e3d364671cb100446b584502c5522a751df Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 17:48:19 -0300 Subject: [PATCH 091/153] Restrict access to confidential issues on search results --- app/services/search/global_service.rb | 2 +- app/services/search/project_service.rb | 3 +- lib/gitlab/project_search_results.rb | 3 +- lib/gitlab/search_results.rb | 7 +- .../lib/gitlab/project_search_results_spec.rb | 69 +++++++++++++- spec/lib/gitlab/search_results_spec.rb | 91 ++++++++++++++++++- 6 files changed, 166 insertions(+), 9 deletions(-) diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index e1e94c5cc3..aa9837038a 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -11,7 +11,7 @@ module Search projects = ProjectsFinder.new.execute(current_user) projects = projects.in_namespace(group.id) if group - Gitlab::SearchResults.new(projects, params[:search]) + Gitlab::SearchResults.new(current_user, projects, params[:search]) end end end diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb index c08881dce4..4b500914cf 100644 --- a/app/services/search/project_service.rb +++ b/app/services/search/project_service.rb @@ -7,7 +7,8 @@ module Search end def execute - Gitlab::ProjectSearchResults.new(project, + Gitlab::ProjectSearchResults.new(current_user, + project, params[:search], params[:repository_ref]) end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 0607a8b959..71c5b6801f 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -2,7 +2,8 @@ module Gitlab class ProjectSearchResults < SearchResults attr_reader :project, :repository_ref - def initialize(project, query, repository_ref = nil) + def initialize(current_user, project, query, repository_ref = nil) + @current_user = current_user @project = project @repository_ref = if repository_ref.present? repository_ref diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index f13528a2ee..f8ab2b1f09 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -1,12 +1,13 @@ module Gitlab class SearchResults - attr_reader :query + attr_reader :current_user, :query # Limit search results by passed projects # It allows us to search only for projects user has access to attr_reader :limit_projects - def initialize(limit_projects, query) + def initialize(current_user, limit_projects, query) + @current_user = current_user @limit_projects = limit_projects || Project.all @query = Shellwords.shellescape(query) if query.present? end @@ -58,7 +59,7 @@ module Gitlab end def issues - issues = Issue.where(project_id: project_ids_relation) + issues = Issue.visible_to_user(current_user).where(project_id: project_ids_relation) if query =~ /#(\d+)\z/ issues = issues.where(iid: $1) diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 09adbc07dc..db0ff95b4f 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -1,11 +1,12 @@ require 'spec_helper' describe Gitlab::ProjectSearchResults, lib: true do + let(:user) { create(:user) } let(:project) { create(:project) } let(:query) { 'hello world' } describe 'initialize with empty ref' do - let(:results) { Gitlab::ProjectSearchResults.new(project, query, '') } + let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, '') } it { expect(results.project).to eq(project) } it { expect(results.repository_ref).to be_nil } @@ -14,10 +15,74 @@ describe Gitlab::ProjectSearchResults, lib: true do describe 'initialize with ref' do let(:ref) { 'refs/heads/test' } - let(:results) { Gitlab::ProjectSearchResults.new(project, query, ref) } + let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, ref) } it { expect(results.project).to eq(project) } it { expect(results.repository_ref).to eq(ref) } it { expect(results.query).to eq('hello world') } end + + describe 'confidential issues' do + let(:query) { 'issue' } + let(:author) { create(:user) } + let(:assignee) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:admin) { create(:admin) } + let!(:issue) { create(:issue, project: project, title: 'Issue 1') } + let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) } + let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) } + + it 'should not list project confidential issues for non project members' do + results = described_class.new(non_member, project, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).not_to include security_issue_1 + expect(issues).not_to include security_issue_2 + expect(results.issues_count).to eq 1 + end + + it 'should list project confidential issues for author' do + results = described_class.new(author, project, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).to include security_issue_1 + expect(issues).not_to include security_issue_2 + expect(results.issues_count).to eq 2 + end + + it 'should list project confidential issues for assignee' do + results = described_class.new(assignee, project.id, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).not_to include security_issue_1 + expect(issues).to include security_issue_2 + expect(results.issues_count).to eq 2 + end + + it 'should list project confidential issues for project members' do + project.team << [member, :developer] + + results = described_class.new(member, project, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).to include security_issue_1 + expect(issues).to include security_issue_2 + expect(results.issues_count).to eq 3 + end + + it 'should list all project issues for admin' do + results = described_class.new(admin, project, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).to include security_issue_1 + expect(issues).to include security_issue_2 + expect(results.issues_count).to eq 3 + end + end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index bb18f41785..f4afe597e8 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Gitlab::SearchResults do + let(:user) { create(:user) } let!(:project) { create(:project, name: 'foo') } let!(:issue) { create(:issue, project: project, title: 'foo') } @@ -9,7 +10,7 @@ describe Gitlab::SearchResults do end let!(:milestone) { create(:milestone, project: project, title: 'foo') } - let(:results) { described_class.new(Project.all, 'foo') } + let(:results) { described_class.new(user, Project.all, 'foo') } describe '#total_count' do it 'returns the total amount of search hits' do @@ -52,4 +53,92 @@ describe Gitlab::SearchResults do expect(results.empty?).to eq(false) end end + + describe 'confidential issues' do + let(:project_1) { create(:empty_project) } + let(:project_2) { create(:empty_project) } + let(:project_3) { create(:empty_project) } + let(:project_4) { create(:empty_project) } + let(:query) { 'issue' } + let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) } + let(:author) { create(:user) } + let(:assignee) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:admin) { create(:admin) } + let!(:issue) { create(:issue, project: project_1, title: 'Issue 1') } + let!(:security_issue_1) { create(:issue, :confidential, project: project_1, title: 'Security issue 1', author: author) } + let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project_1, assignee: assignee) } + let!(:security_issue_3) { create(:issue, :confidential, project: project_2, title: 'Security issue 3', author: author) } + let!(:security_issue_4) { create(:issue, :confidential, project: project_3, title: 'Security issue 4', assignee: assignee) } + let!(:security_issue_5) { create(:issue, :confidential, project: project_4, title: 'Security issue 5') } + + it 'should not list confidential issues for non project members' do + results = described_class.new(non_member, limit_projects, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).not_to include security_issue_1 + expect(issues).not_to include security_issue_2 + expect(issues).not_to include security_issue_3 + expect(issues).not_to include security_issue_4 + expect(issues).not_to include security_issue_5 + expect(results.issues_count).to eq 1 + end + + it 'should list confidential issues for author' do + results = described_class.new(author, limit_projects, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).to include security_issue_1 + expect(issues).not_to include security_issue_2 + expect(issues).to include security_issue_3 + expect(issues).not_to include security_issue_4 + expect(issues).not_to include security_issue_5 + expect(results.issues_count).to eq 3 + end + + it 'should list confidential issues for assignee' do + results = described_class.new(assignee, limit_projects, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).not_to include security_issue_1 + expect(issues).to include security_issue_2 + expect(issues).not_to include security_issue_3 + expect(issues).to include security_issue_4 + expect(issues).not_to include security_issue_5 + expect(results.issues_count).to eq 3 + end + + it 'should list confidential issues for project members' do + project_1.team << [member, :developer] + project_2.team << [member, :developer] + + results = described_class.new(member, limit_projects, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).to include security_issue_1 + expect(issues).to include security_issue_2 + expect(issues).to include security_issue_3 + expect(issues).not_to include security_issue_4 + expect(issues).not_to include security_issue_5 + expect(results.issues_count).to eq 4 + end + + it 'should list all issues for admin' do + results = described_class.new(admin, limit_projects, query) + issues = results.objects('issues') + + expect(issues).to include issue + expect(issues).to include security_issue_1 + expect(issues).to include security_issue_2 + expect(issues).to include security_issue_3 + expect(issues).to include security_issue_4 + expect(issues).not_to include security_issue_5 + expect(results.issues_count).to eq 5 + end + end end From 1029f4101b1de98ec7ffa706530bf405ffa71060 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 17:53:21 -0300 Subject: [PATCH 092/153] Fix issues count on project view --- app/helpers/application_helper.rb | 2 +- app/views/layouts/nav/_project.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 883c287174..e6ceb21353 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -301,7 +301,7 @@ module ApplicationHelper if project.nil? nil elsif current_controller?(:issues) - project.issues.send(entity).count + project.issues.visible_to_user(current_user).send(entity).count elsif current_controller?(:merge_requests) project.merge_requests.send(entity).count end diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 0ae83ee01e..86b46e8c75 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -67,7 +67,7 @@ %span Issues - if @project.default_issues_tracker? - %span.count.issue_counter= number_with_delimiter(@project.issues.opened.count) + %span.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count) - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do From 482bfd1a6d454652feb1f64c0c0e1c50ce701986 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 17:59:30 -0300 Subject: [PATCH 093/153] Restrict access for confidential issues on milestone view --- app/helpers/milestones_helper.rb | 2 +- app/models/concerns/milestoneish.rb | 20 ++-- app/models/milestone.rb | 4 +- app/views/projects/milestones/show.html.haml | 2 +- .../shared/milestones/_milestone.html.haml | 4 +- .../shared/milestones/_summary.html.haml | 8 +- app/views/shared/milestones/_tabs.html.haml | 4 +- app/views/shared/milestones/_top.html.haml | 4 +- spec/models/concerns/milestoneish_spec.rb | 104 ++++++++++++++++++ spec/models/milestone_spec.rb | 20 ++-- 10 files changed, 140 insertions(+), 32 deletions(-) create mode 100644 spec/models/concerns/milestoneish_spec.rb diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index e8ac8788d9..92ed0891e9 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -38,7 +38,7 @@ module MilestonesHelper def milestone_progress_bar(milestone) options = { class: 'progress-bar progress-bar-success', - style: "width: #{milestone.percent_complete}%;" + style: "width: #{milestone.percent_complete(current_user)}%;" } content_tag :div, class: 'progress' do diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index d67df7c1d9..5b8e3f654e 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -1,18 +1,18 @@ module Milestoneish - def closed_items_count - issues.closed.size + merge_requests.closed_and_merged.size + def closed_items_count(user = nil) + issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size end - def total_items_count - issues.size + merge_requests.size + def total_items_count(user = nil) + issues_visible_to_user(user).size + merge_requests.size end - def complete? - total_items_count == closed_items_count + def complete?(user = nil) + total_items_count(user) == closed_items_count(user) end - def percent_complete - ((closed_items_count * 100) / total_items_count).abs + def percent_complete(user = nil) + ((closed_items_count(user) * 100) / total_items_count(user)).abs rescue ZeroDivisionError 0 end @@ -22,4 +22,8 @@ module Milestoneish (due_date - Date.today).to_i end + + def issues_visible_to_user(user = nil) + issues.visible_to_user(user) + end end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 374590ba0c..de7183bf6b 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -121,8 +121,8 @@ class Milestone < ActiveRecord::Base active? && issues.opened.count.zero? end - def is_empty? - total_items_count.zero? + def is_empty?(user = nil) + total_items_count(user).zero? end def author_id diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index b4597043a2..be63875ab3 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -42,7 +42,7 @@ = preserve do = markdown @milestone.description -- if @milestone.complete? && @milestone.active? +- if @milestone.complete?(current_user) && @milestone.active? .alert.alert-success.prepend-top-default %span All issues for this milestone are closed. You may close milestone now. diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index f01138af3f..6b25745c55 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -6,10 +6,10 @@ .col-sm-6 %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path .col-sm-6 - .pull-right.light #{milestone.percent_complete}% complete + .pull-right.light #{milestone.percent_complete(current_user)}% complete .row .col-sm-6 - = link_to pluralize(milestone.issues.size, 'Issue'), issues_path + = link_to pluralize(milestone.issues_visible_to_user(current_user).size, 'Issue'), issues_path · = link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path .col-sm-6= milestone_progress_bar(milestone) diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml index 59d4ae29f7..385c659660 100644 --- a/app/views/shared/milestones/_summary.html.haml +++ b/app/views/shared/milestones/_summary.html.haml @@ -3,15 +3,15 @@ .context.prepend-top-default .milestone-summary %h4 Progress - %strong= milestone.issues.size + %strong= milestone.issues_visible_to_user(current_user).size issues: %span.milestone-stat - %strong= milestone.issues.opened.size + %strong= milestone.issues_visible_to_user(current_user).opened.size open and - %strong= milestone.issues.closed.size + %strong= milestone.issues_visible_to_user(current_user).closed.size closed %span.milestone-stat - %strong== #{milestone.percent_complete}% + %strong== #{milestone.percent_complete(current_user)}% complete %span.milestone-stat diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml index 57d7ee85a3..2b6ce2d7e7 100644 --- a/app/views/shared/milestones/_tabs.html.haml +++ b/app/views/shared/milestones/_tabs.html.haml @@ -2,7 +2,7 @@ %li.active = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do Issues - %span.badge= milestone.issues.size + %span.badge= milestone.issues_visible_to_user(current_user).size %li = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do Merge Requests @@ -21,7 +21,7 @@ .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 + = render 'shared/milestones/issues_tab', issues: milestone.issues_visible_to_user(current_user), 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 diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 4cf1d948b5..cab8743a07 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -28,7 +28,7 @@ %h2.title = markdown escape_once(milestone.title), pipeline: :single_line -- if milestone.complete? && milestone.active? +- if milestone.complete?(current_user) && 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} @@ -47,7 +47,7 @@ - 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 + = ms.issues_visible_to_user(current_user).opened.count %td - if ms.closed? Closed diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb new file mode 100644 index 0000000000..47c3be673c --- /dev/null +++ b/spec/models/concerns/milestoneish_spec.rb @@ -0,0 +1,104 @@ +require 'spec_helper' + +describe Milestone, 'Milestoneish' do + let(:author) { create(:user) } + let(:assignee) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:admin) { create(:admin) } + let(:project) { create(:project, :public) } + let(:milestone) { create(:milestone, project: project) } + let!(:issue) { create(:issue, project: project, milestone: milestone) } + let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) } + let!(:security_issue_2) { create(:issue, :confidential, project: project, assignee: assignee, milestone: milestone) } + let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) } + let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) } + let!(:closed_security_issue_1) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) } + let!(:closed_security_issue_2) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) } + let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) } + let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) } + + before do + project.team << [member, :developer] + end + + describe '#closed_items_count' do + it 'should not count confidential issues for non project members' do + expect(milestone.closed_items_count(non_member)).to eq 2 + end + + it 'should count confidential issues for author' do + expect(milestone.closed_items_count(author)).to eq 4 + end + + it 'should count confidential issues for assignee' do + expect(milestone.closed_items_count(assignee)).to eq 4 + end + + it 'should count confidential issues for project members' do + expect(milestone.closed_items_count(member)).to eq 6 + end + + it 'should count all issues for admin' do + expect(milestone.closed_items_count(admin)).to eq 6 + end + end + + describe '#total_items_count' do + it 'should not count confidential issues for non project members' do + expect(milestone.total_items_count(non_member)).to eq 4 + end + + it 'should count confidential issues for author' do + expect(milestone.total_items_count(author)).to eq 7 + end + + it 'should count confidential issues for assignee' do + expect(milestone.total_items_count(assignee)).to eq 7 + end + + it 'should count confidential issues for project members' do + expect(milestone.total_items_count(member)).to eq 10 + end + + it 'should count all issues for admin' do + expect(milestone.total_items_count(admin)).to eq 10 + end + end + + describe '#complete?' do + it 'returns false when has items opened' do + expect(milestone.complete?(non_member)).to eq false + end + + it 'returns true when all items are closed' do + issue.close + merge_request.close + + expect(milestone.complete?(non_member)).to eq true + end + end + + describe '#percent_complete' do + it 'should not count confidential issues for non project members' do + expect(milestone.percent_complete(non_member)).to eq 50 + end + + it 'should count confidential issues for author' do + expect(milestone.percent_complete(author)).to eq 57 + end + + it 'should count confidential issues for assignee' do + expect(milestone.percent_complete(assignee)).to eq 57 + end + + it 'should count confidential issues for project members' do + expect(milestone.percent_complete(member)).to eq 60 + end + + it 'should count confidential issues for admin' do + expect(milestone.percent_complete(admin)).to eq 60 + end + end +end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index de1757bf67..72a4ea7022 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -32,6 +32,7 @@ describe Milestone, models: true do let(:milestone) { create(:milestone) } let(:issue) { create(:issue) } + let(:user) { create(:user) } describe "unique milestone title per project" do it "shouldn't accept the same title in a project twice" do @@ -50,18 +51,17 @@ describe Milestone, models: true do describe "#percent_complete" do it "should not count open issues" do milestone.issues << issue - expect(milestone.percent_complete).to eq(0) + expect(milestone.percent_complete(user)).to eq(0) end it "should count closed issues" do issue.close milestone.issues << issue - expect(milestone.percent_complete).to eq(100) + expect(milestone.percent_complete(user)).to eq(100) end it "should recover from dividing by zero" do - expect(milestone.issues).to receive(:size).and_return(0) - expect(milestone.percent_complete).to eq(0) + expect(milestone.percent_complete(user)).to eq(0) end end @@ -103,7 +103,7 @@ describe Milestone, models: true do ) end - it { expect(milestone.percent_complete).to eq(75) } + it { expect(milestone.percent_complete(user)).to eq(75) } end describe :items_count do @@ -113,23 +113,23 @@ describe Milestone, models: true do milestone.merge_requests << create(:merge_request) end - it { expect(milestone.closed_items_count).to eq(1) } - it { expect(milestone.total_items_count).to eq(3) } - it { expect(milestone.is_empty?).to be_falsey } + it { expect(milestone.closed_items_count(user)).to eq(1) } + it { expect(milestone.total_items_count(user)).to eq(3) } + it { expect(milestone.is_empty?(user)).to be_falsey } end describe :can_be_closed? do it { expect(milestone.can_be_closed?).to be_truthy } end - describe :is_empty? do + describe :total_items_count do before do create :closed_issue, milestone: milestone create :merge_request, milestone: milestone end it 'Should return total count of issues and merge requests assigned to milestone' do - expect(milestone.total_items_count).to eq 2 + expect(milestone.total_items_count(user)).to eq 2 end end From 7d403ec46ffd5778a68eebb9117e08f605938b15 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 18:00:03 -0300 Subject: [PATCH 094/153] Add eye-slash icon to confidential issues --- app/helpers/issues_helper.rb | 4 ++++ app/views/projects/issues/_issue.html.haml | 1 + app/views/projects/issues/show.html.haml | 1 + app/views/search/results/_issue.html.haml | 1 + app/views/shared/milestones/_issuable.html.haml | 2 ++ 5 files changed, 9 insertions(+) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index ae4ebc0854..e00d320402 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -98,6 +98,10 @@ module IssuesHelper end.sort.to_sentence(last_word_connector: ', or ') end + def confidential_icon(issue) + icon('eye-slash') if issue.confidential? + end + def emoji_icon(name, unicode = nil, aliases = []) unicode ||= Emoji.emoji_filename(name) rescue "" diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index a44f34c2a6..00e1a3d806 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -5,6 +5,7 @@ .issue-title %span.issue-title-text + = confidential_icon(issue) = link_to_gfm issue.title, issue_path(issue), class: "title" %ul.controls.light - if issue.closed? diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index c3ee5c80e5..ce5b84ee71 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -22,6 +22,7 @@ = icon('angle-double-left') .issue-meta + = confidential_icon(@issue) %strong.identifier Issue ##{@issue.iid} %span.creator diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index 45d700781f..710f5613c8 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -1,5 +1,6 @@ .search-result-row %h4 + = confidential_icon(issue) = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do %span.term.str-truncated= issue.title .pull-right ##{issue.iid} diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index f7c6fc14ad..8588809672 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -10,6 +10,8 @@ %strong #{project.name} · - elsif show_full_project_name %strong #{project.name_with_namespace} · + - if issuable.is_a?(Issue) + = confidential_icon(issuable) = 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 From 9222459ea36ce7bfafdf76742a5a44db7957db8d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 18:03:10 -0300 Subject: [PATCH 095/153] Restrict access to confidential issues on activity feed --- app/helpers/events_helper.rb | 2 +- app/models/event.rb | 6 ++++-- app/views/events/_event.html.haml | 2 +- features/steps/groups.rb | 2 +- spec/models/event_spec.rb | 36 +++++++++++++++++++++++++++++++ 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 37a888d9c6..a67a6b208e 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -194,7 +194,7 @@ module EventsHelper end def event_to_atom(xml, event) - if event.proper? + if event.proper?(current_user) xml.entry do event_link = event_feed_url(event) event_title = event_feed_title(event) diff --git a/app/models/event.rb b/app/models/event.rb index 9a0bbf50f8..a5cfeaf388 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -73,15 +73,17 @@ class Event < ActiveRecord::Base end end - def proper? + def proper?(user = nil) if push? true elsif membership_changed? true elsif created_project? true + elsif issue? + Ability.abilities.allowed?(user, :read_issue, issue) else - ((issue? || merge_request? || note?) && target) || milestone? + ((merge_request? || note?) && target) || milestone? end end diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 36fb2d5162..2d9d9dd634 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -1,4 +1,4 @@ -- if event.proper? +- if event.proper?(current_user) .event-item{class: "#{event.body? ? "event-block" : "event-inline" }"} .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 7a6ae15ffa..e5b7db4c5e 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -35,7 +35,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should see projects activity feed' do - expect(page).to have_content 'closed issue' + expect(page).to have_content 'joined project' end step 'I should see issues from group "Owned" assigned to me' do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index ec2a923f91..5fe4424673 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -65,6 +65,42 @@ describe Event, models: true do it { expect(@event.author).to eq(@user) } end + describe '#proper?' do + context 'issue event' do + let(:project) { create(:empty_project, :public) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:author) { create(:author) } + let(:assignee) { create(:user) } + let(:admin) { create(:admin) } + let(:event) { Event.new(project: project, action: Event::CREATED, target: issue, author_id: author.id) } + + before do + project.team << [member, :developer] + end + + context 'for non confidential issues' do + let(:issue) { create(:issue, project: project, author: author, assignee: assignee) } + + it { expect(event.proper?(non_member)).to eq true } + it { expect(event.proper?(author)).to eq true } + it { expect(event.proper?(assignee)).to eq true } + it { expect(event.proper?(member)).to eq true } + it { expect(event.proper?(admin)).to eq true } + end + + context 'for confidential issues' do + let(:issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) } + + it { expect(event.proper?(non_member)).to eq false } + it { expect(event.proper?(author)).to eq true } + it { expect(event.proper?(assignee)).to eq true } + it { expect(event.proper?(member)).to eq true } + it { expect(event.proper?(admin)).to eq true } + end + end + end + describe '.limit_recent' do let!(:event1) { create(:closed_issue_event) } let!(:event2) { create(:closed_issue_event) } From 5ed7e2cfd5de7c1db0374594428895e84e98321e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 17 Mar 2016 18:03:39 -0300 Subject: [PATCH 096/153] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 102908102e..d0b68ac07b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.6.0 (unreleased) + - Add confidential issues - Bump gitlab_git to 9.0.3 (Stan Hu) - Support Golang subpackage fetching (Stan Hu) - Bump Capybara gem to 2.6.2 (Stan Hu) From 0901d5172429630c8d1ea6d8e91be71a2e690043 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 17 Mar 2016 16:25:21 -0700 Subject: [PATCH 097/153] Improve award emoji test reliability by checking that the emoji is displayed before the search field is focused --- features/project/issues/award_emoji.feature | 11 +++++++---- features/steps/project/issues/award_emoji.rb | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature index 2945bb3753..f0fd414a9f 100644 --- a/features/project/issues/award_emoji.feature +++ b/features/project/issues/award_emoji.feature @@ -18,21 +18,24 @@ Feature: Award Emoji @javascript Scenario: I add and remove custom award in the issue Given I click to emoji-picker - Then The search field is focused - And I click to emoji in the picker + Then The emoji menu is visible + And The search field is focused + Then I click to emoji in the picker Then I have award added And I can remove it by clicking to icon @javascript Scenario: I can see the list of emoji categories Given I click to emoji-picker - Then The search field is focused + Then The emoji menu is visible + And The search field is focused Then I can see the activity and food categories @javascript Scenario: I can search emoji Given I click to emoji-picker - Then The search field is focused + Then The emoji menu is visible + And The search field is focused And I search "hand" Then I see search result for "hand" diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index ce2554bc80..c5d45709b4 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -92,6 +92,10 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end end + step 'The emoji menu is visible' do + page.find(".emoji-menu.is-visible") + end + step 'The search field is focused' do expect(page).to have_selector('#emoji_search') expect(page.evaluate_script('document.activeElement.id')).to eq('emoji_search') From 4eb9711b468c9e450e726bba0e5c289cb0f0b1a9 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Thu, 17 Mar 2016 21:52:34 -0700 Subject: [PATCH 098/153] Remove CI duplication. --- doc/README.md | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/doc/README.md b/doc/README.md index db19c3de8d..08d0a6a5bf 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,6 +3,7 @@ ## User documentation - [API](api/README.md) Automate GitLab via a simple and powerful API. +- [CI](ci/README.md) - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). @@ -16,42 +17,6 @@ - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. -## CI User documentation - -- [Get started with GitLab CI](ci/quick_start/README.md) -- [Learn how to enable or disable GitLab CI](ci/enable_or_disable_ci.md) -- [Learn how `.gitlab-ci.yml` works](ci/yaml/README.md) -- [Configure a Runner, the application that runs your builds](ci/runners/README.md) -- [Use Docker images with GitLab Runner](ci/docker/using_docker_images.md) -- [Use CI to build Docker images](ci/docker/using_docker_build.md) -- [Use variables in your `.gitlab-ci.yml`](ci/variables/README.md) -- [Use SSH keys in your build environment](ci/ssh_keys/README.md) -- [Trigger builds through the API](ci/triggers/README.md) -- [Build artifacts](ci/build_artifacts/README.md) -- [User permissions](ci/permissions/README.md) -- [API](ci/api/README.md) - -### CI Examples - -- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) -- [Test your PHP applications](ci/examples/php.md) -- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md) -- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md) -- [Test Clojure applications](ci/examples/test-clojure-application.md) -- [Using `dpl` as deployment tool](ci/deployment/README.md) -- Help your favorite programming language and GitLab by sending a merge request - with a guide for that language. - -### CI Services - -GitLab CI uses the `services` keyword to define what docker containers should -be linked with your base image. Below is a list of examples you may use: - -- [Using MySQL](ci/services/mysql.md) -- [Using PostgreSQL](ci/services/postgres.md) -- [Using Redis](ci/services/redis.md) -- [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services) - ## Administrator documentation - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough. From 3e98e5971b76d3847a1abd361bc120bf8bcfa214 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 4 Mar 2016 17:37:07 +0000 Subject: [PATCH 099/153] Improved issue sidebar Updated the spacing throughout the sidebar so that it fits better on smaller screens Added more participants button Closes #13353 --- app/assets/javascripts/issue.js.coffee | 24 +++ app/assets/stylesheets/pages/issuable.scss | 146 +++++++++++------- app/helpers/projects_helper.rb | 4 +- .../shared/issuable/_participants.html.haml | 13 +- app/views/shared/issuable/_sidebar.html.haml | 46 +++--- 5 files changed, 145 insertions(+), 88 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index d663e34871..47db7c63ec 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -7,6 +7,7 @@ class @Issue # Prevent duplicate event bindings @disableTaskList() @fixAffixScroll() + @initParticipants() if $('a.btn-close').length @initTaskList() @initIssueBtnEventListeners() @@ -84,3 +85,26 @@ class @Issue type: 'PATCH' url: $('form.js-issuable-update').attr('action') data: patchData + + initParticipants: -> + $(document).on "click", ".js-participants-more", @toggleHiddenParticipants + + # hide any participants from number 6 + $(".js-participants-author").each (i) -> + if i > 6 + $(@) + .addClass "js-participants-hidden hidden" + + toggleHiddenParticipants: (e) -> + e.preventDefault() + + currentText = $(this).text().trim() + lessText = $(this).data("less-text") + originalText = $(this).data("original-text") + + if currentText is originalText + $(this).text(lessText) + else + $(this).text(originalText) + + $(".js-participants-hidden").toggleClass "hidden" diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 6f93299404..3c0fe8e6e4 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -1,34 +1,3 @@ -@media (max-width: $screen-sm-max) { - .issuable-affix { - margin-top: 20px; - } -} - -@media (max-width: $screen-md-max) { - .issuable-affix { - position: static; - } -} - -@media (min-width: $screen-md-max) { - .issuable-affix { - &.affix-top { - position: static; - } - - &.affix { - position: fixed; - top: 70px; - margin-right: 35px; - - &.no-affix { - position: relative; - top: 0; - } - } - } -} - .issuable-details { section { .issuable-discussion { @@ -54,6 +23,10 @@ padding: 6px 10px; } } + + &.has-labels { + margin-bottom: -5px; + } } .issuable-sidebar { @@ -66,8 +39,9 @@ width: $gutter_inner_width; // -- - &:first-child { - padding-top: 5px; + &.issuable-sidebar-header { + padding-top: 0; + padding-bottom: 10px; } &:last-child { @@ -75,7 +49,6 @@ } span { - margin-top: 7px; display: inline-block; } @@ -84,7 +57,7 @@ } .issuable-count { - + margin-top: 7px; } .gutter-toggle { @@ -99,19 +72,19 @@ .title { color: $gl-text-color; - margin-bottom: 8px; + margin-bottom: 10px; + line-height: 1; .avatar { margin-left: 0; } - label { - font-weight: normal; - margin-right: 4px; - } - .edit-link { color: $gl-gray; + + &:hover { + color: $md-link-color; + } } } @@ -144,11 +117,6 @@ .btn-clipboard { color: $gl-gray; } - - .participants .avatar { - margin-top: 6px; - margin-right: 2px; - } } .right-sidebar { @@ -163,8 +131,12 @@ &.right-sidebar-expanded { width: $gutter_width; - hr { - display: none; + .value { + line-height: 1; + } + + .bold { + font-weight: 600; } .sidebar-collapsed-icon { @@ -172,8 +144,23 @@ } .gutter-toggle { + margin-top: 7px; border-left: 1px solid $border-gray-light; } + + .avatar { + float: left; + margin-right: 10px; + margin-bottom: 0; + margin-left: 0; + } + + .username { + display: block; + margin-top: 4px; + font-size: 13px; + font-weight: normal; + } } .subscribe-button { @@ -193,14 +180,6 @@ width: $sidebar_collapsed_width; padding-top: 0; - hr { - margin: 0; - color: $gray-normal; - border-color: $gray-normal; - width: 62px; - margin-left: -20px - } - .block { width: $sidebar_collapsed_width - 1px; margin-left: -19px; @@ -209,12 +188,18 @@ overflow: hidden; } + .participants { + border-bottom: 1px solid $border-gray-light; + } + .hide-collapsed { display: none; } .gutter-toggle { - margin-left: -36px; + width: 100%; + margin-left: 0; + padding-left: 25px; } .sidebar-collapsed-icon { @@ -241,6 +226,11 @@ } } } + + .sidebar-collapsed-user { + padding-bottom: 0; + margin-bottom: 10px; + } } .btn { @@ -251,6 +241,13 @@ border: 1px solid $border-gray-dark; } } + + a { + &:hover { + color: $md-link-color; + text-decoration: none; + } + } } .btn-default.gutter-toggle { @@ -270,3 +267,38 @@ color: $gray-darkest; } } + +.participants-list { + margin: -5px -5px; +} + +.participants-author { + display: inline-block; + padding: 5px 5px; + + .author_link { + display: block; + } + + .avatar.avatar-inline { + margin: 0; + } +} + +.participants-more { + margin-top: 5px; + margin-left: 5px; + + a { + color: #8c8c8c; + + &:hover { + color: $gl-text-color; + text-decoration: none; + } + + &:focus { + text-decoration: none; + } + } +} diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index b5acb80b72..5473419ef2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -26,7 +26,7 @@ module ProjectsHelper image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] end - def link_to_member(project, author, opts = {}) + def link_to_member(project, author, opts = {}, &block) default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } opts = default_opts.merge(opts) @@ -44,6 +44,8 @@ module ProjectsHelper author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] end + author_html << capture(&block) if block + author_html = author_html.html_safe if opts[:name] diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml index f1d92ef48b..8fe1c9af11 100644 --- a/app/views/shared/issuable/_participants.html.haml +++ b/app/views/shared/issuable/_participants.html.haml @@ -1,3 +1,5 @@ +- participants_size = participants.size +- participants_extra = participants_size - 7 .block.participants .sidebar-collapsed-icon = icon('users') @@ -5,6 +7,11 @@ = participants.count .title.hide-collapsed = pluralize participants.count, "participant" - - participants.each do |participant| - %span.hide-collapsed - = link_to_member(@project, participant, name: false, size: 24) + .hide-collapsed.participants-list + - participants.each do |participant| + .participants-author.js-participants-author + = link_to_member(@project, participant, name: false, size: 24) + - if participants_extra > 0 + %div.participants-more + %a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}} + + #{participants_size - 7} more diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 23b1ed1e51..6f07571f24 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,13 +1,12 @@ %aside.right-sidebar{ class: sidebar_gutter_collapsed_class } .issuable-sidebar - .block + .block.issuable-sidebar-header %span.issuable-count.hide-collapsed.pull-left = issuable.iid of = issuables_count(issuable) - %span.pull-right - %a.gutter-toggle.js-sidebar-toggle{href: '#'} - = sidebar_gutter_toggle_icon + %a.gutter-toggle.pull-right.js-sidebar-toggle{href: '#'} + = sidebar_gutter_toggle_icon .issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'} - if prev_issuable = prev_issuable_for(issuable) = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn' @@ -22,20 +21,20 @@ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| .block.assignee - .sidebar-collapsed-icon + .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: issuable.assignee.to_reference} - if issuable.assignee = link_to_member_avatar(issuable.assignee, size: 24) - else = icon('user') .title.hide-collapsed - %label - Assignee + Assignee - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - .pull-right - = link_to 'Edit', '#', class: 'edit-link' - .value.hide-collapsed + = link_to 'Edit', '#', class: 'edit-link pull-right' + .value.bold.hide-collapsed - if issuable.assignee - %strong= link_to_member(@project, issuable.assignee, size: 24) + = link_to_member(@project, issuable.assignee, size: 32) do + %span.username + = issuable.assignee.to_reference - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) %a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'} = icon('exclamation-triangle') @@ -54,18 +53,13 @@ - else No .title.hide-collapsed - %label - Milestone + Milestone - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - .pull-right - = link_to 'Edit', '#', class: 'edit-link' - .value.hide-collapsed + = link_to 'Edit', '#', class: 'edit-link pull-right' + .value.bold.hide-collapsed - if issuable.milestone - %span.back-to-milestone - = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do - %strong - = icon('clock-o') - = issuable.milestone.title + = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do + = issuable.milestone.title - else .light None .selectbox.hide-collapsed @@ -80,11 +74,10 @@ %span = issuable.labels.count .title.hide-collapsed - %label Labels + Labels - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - .pull-right - = link_to 'Edit', '#', class: 'edit-link' - .value.issuable-show-labels.hide-collapsed + = link_to 'Edit', '#', class: 'edit-link pull-right' + .value.issuable-show-labels.hide-collapsed{class: ("has-labels" if issuable.labels.any?)} - if issuable.labels.any? - issuable.labels.each do |label| = link_to_label(label, type: issuable.to_ability_name) @@ -95,14 +88,13 @@ { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" } = render "shared/issuable/participants", participants: issuable.participants(current_user) - %hr - if current_user - subscribed = issuable.subscribed?(current_user) .block.light.subscription{data: {url: toggle_subscription_path(issuable)}} .sidebar-collapsed-icon = icon('rss') .title.hide-collapsed - %label.light Notifications + Notifications - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' %button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'} %span= subscribed ? 'Unsubscribe' : 'Subscribe' From 704cf9df2b610893412524efcdbc81b204de6d46 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 4 Mar 2016 17:39:48 +0000 Subject: [PATCH 100/153] Fixed tooltip issue for participants --- app/assets/stylesheets/pages/issuable.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 3c0fe8e6e4..dd4d867c48 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -148,7 +148,7 @@ border-left: 1px solid $border-gray-light; } - .avatar { + .assignee .avatar { float: left; margin-right: 10px; margin-bottom: 0; From e0f47c3489d082df7f62d622ac273ae1354b50a9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sun, 6 Mar 2016 12:06:52 +0000 Subject: [PATCH 101/153] Fixed issue when user doesnt exist Fixed button hover color --- app/assets/stylesheets/pages/issuable.scss | 11 +---------- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index dd4d867c48..7e6ab5d5c6 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -242,7 +242,7 @@ } } - a { + a:not(.btn) { &:hover { color: $md-link-color; text-decoration: none; @@ -291,14 +291,5 @@ a { color: #8c8c8c; - - &:hover { - color: $gl-text-color; - text-decoration: none; - } - - &:focus { - text-decoration: none; - } } } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 6f07571f24..eacb2837ab 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -21,7 +21,7 @@ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| .block.assignee - .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: issuable.assignee.to_reference} + .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)} - if issuable.assignee = link_to_member_avatar(issuable.assignee, size: 24) - else From 021d79e3ac1cfcac875866f1d70176552d1bf159 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 7 Mar 2016 08:40:19 +0000 Subject: [PATCH 102/153] Links to user on collapsed issue sidebar --- app/assets/stylesheets/pages/issuable.scss | 4 ++++ app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 7e6ab5d5c6..2760af8a48 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -214,6 +214,10 @@ margin-top: 0; } + .author { + display: none; + } + .btn-clipboard { border: none; diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index eacb2837ab..2b95b19fac 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -23,7 +23,7 @@ .block.assignee .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)} - if issuable.assignee - = link_to_member_avatar(issuable.assignee, size: 24) + = link_to_member(@project, issuable.assignee, size: 24) - else = icon('user') .title.hide-collapsed From 38ea9c6cf7c7c063f841ebe95477e9f48d7bc430 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 10 Mar 2016 17:09:57 +0000 Subject: [PATCH 103/153] Reused some variables Set global JS variable to sync view up --- app/assets/javascripts/issue.js.coffee | 4 ++-- app/views/shared/issuable/_participants.html.haml | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 47db7c63ec..424c354b75 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -91,7 +91,7 @@ class @Issue # hide any participants from number 6 $(".js-participants-author").each (i) -> - if i > 6 + if i >= PARTICIPANTS_ROW $(@) .addClass "js-participants-hidden hidden" @@ -101,7 +101,7 @@ class @Issue currentText = $(this).text().trim() lessText = $(this).data("less-text") originalText = $(this).data("original-text") - + if currentText is originalText $(this).text(lessText) else diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml index 8fe1c9af11..75a330c9e5 100644 --- a/app/views/shared/issuable/_participants.html.haml +++ b/app/views/shared/issuable/_participants.html.haml @@ -1,5 +1,8 @@ +- participants_row = 7 - participants_size = participants.size -- participants_extra = participants_size - 7 +- participants_extra = participants_size - participants_row +:javascript + var PARTICIPANTS_ROW = #{participants_row}; .block.participants .sidebar-collapsed-icon = icon('users') @@ -14,4 +17,4 @@ - if participants_extra > 0 %div.participants-more %a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}} - + #{participants_size - 7} more + + #{participants_extra} more From b2e673e523ec0e6ff3d2c27d113e9d6a74a80d7a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 15 Mar 2016 11:10:42 +0000 Subject: [PATCH 104/153] Removed global JS var --- app/assets/javascripts/issue.js.coffee | 9 +++++---- app/views/shared/issuable/_participants.html.haml | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 424c354b75..f50df1f5ea 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -87,13 +87,14 @@ class @Issue data: patchData initParticipants: -> + _this = @ $(document).on "click", ".js-participants-more", @toggleHiddenParticipants - # hide any participants from number 6 $(".js-participants-author").each (i) -> - if i >= PARTICIPANTS_ROW + if i >= _this.PARTICIPANTS_ROW_COUNT $(@) - .addClass "js-participants-hidden hidden" + .addClass "js-participants-hidden" + .hide() toggleHiddenParticipants: (e) -> e.preventDefault() @@ -107,4 +108,4 @@ class @Issue else $(this).text(originalText) - $(".js-participants-hidden").toggleClass "hidden" + $(".js-participants-hidden").toggle() diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml index 75a330c9e5..3fb409ff72 100644 --- a/app/views/shared/issuable/_participants.html.haml +++ b/app/views/shared/issuable/_participants.html.haml @@ -1,8 +1,6 @@ - participants_row = 7 - participants_size = participants.size - participants_extra = participants_size - participants_row -:javascript - var PARTICIPANTS_ROW = #{participants_row}; .block.participants .sidebar-collapsed-icon = icon('users') @@ -18,3 +16,5 @@ %div.participants-more %a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}} + #{participants_extra} more +:javascript + Issue.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row}; From 8357a23ac81d16c566542b363d1dcb893aafc51c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 15 Mar 2016 17:24:01 +0000 Subject: [PATCH 105/153] Fixes issue with any milestone value in dropdown not being selectable Closes #14293 --- app/assets/javascripts/milestone_select.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 5e884454a6..dd2a35bef4 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -46,7 +46,7 @@ class @MilestoneSelect milestone.title id: (milestone) -> if !useId - if milestone.title isnt "Any milestone" + if milestone.title isnt "Any Milestone" milestone.title else "" From 72830aa8095bffeb844c71d5b202a1b1b9213eb8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 08:53:57 +0000 Subject: [PATCH 106/153] Removed comparing against text --- app/assets/javascripts/milestone_select.js.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index dd2a35bef4..86b806f53a 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -30,6 +30,7 @@ class @MilestoneSelect if showAny data.unshift( + any: true title: 'Any Milestone' ) @@ -46,7 +47,7 @@ class @MilestoneSelect milestone.title id: (milestone) -> if !useId - if milestone.title isnt "Any Milestone" + if !milestone.any? milestone.title else "" From fa6fae18a95656c0f300fff86214ad62626131b2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 18 Mar 2016 09:49:44 +0000 Subject: [PATCH 107/153] Changed any variable name to save confusion --- app/assets/javascripts/milestone_select.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 86b806f53a..32159a7c17 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -30,7 +30,7 @@ class @MilestoneSelect if showAny data.unshift( - any: true + isAny: true title: 'Any Milestone' ) @@ -47,7 +47,7 @@ class @MilestoneSelect milestone.title id: (milestone) -> if !useId - if !milestone.any? + if !milestone.isAny? milestone.title else "" From 3f0d780c19d821e74a4a89634ada10dedec0dbeb Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 11 Mar 2016 17:40:59 +0100 Subject: [PATCH 108/153] Show a notice for diffs that are too large This builds on the changes introduced in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/72 and results in merge requests with large diffs (e.g. due to them containing minified CSS) loading much faster. --- Gemfile | 2 +- Gemfile.lock | 6 +++--- app/views/projects/diffs/_file.html.haml | 22 +++++++++++++--------- lib/gitlab/diff/file.rb | 4 ++++ spec/lib/gitlab/diff/file_spec.rb | 14 ++++++++++++++ 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Gemfile b/Gemfile index a3fb6779e9..e500bfb788 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,7 @@ gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 9.0' +gem "gitlab_git", '~> 10.0' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 7b0dd83da5..63ed9441c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -359,11 +359,11 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.3.1) gemojione (~> 2.2, >= 2.2.1) - gitlab_git (9.0.3) + gitlab_git (10.0.0) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) - rugged (~> 0.24.0b13) + rugged (~> 0.24.0) gitlab_meta (7.0) gitlab_omniauth-ldap (1.2.1) net-ldap (~> 0.9) @@ -942,7 +942,7 @@ DEPENDENCIES github-markup (~> 1.3.1) gitlab-flowdock-git-hook (~> 1.0.1) gitlab_emoji (~> 0.3.0) - gitlab_git (~> 9.0) + gitlab_git (~> 10.0) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.1.0) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 3ac058a3bf..dc34032b1b 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -42,13 +42,17 @@ .diff-content.diff-wrap-lines -# Skipp all non non-supported blobs - return unless blob.respond_to?('text?') - - if blob_text_viewable?(blob) - - if diff_view == 'parallel' - = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - - else - = render "projects/diffs/text_file", diff_file: diff_file, index: i - - elsif blob.image? - - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file) - = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i + - if diff_file.too_large? + .nothing-here-block + This diff could not be displayed because it is too large. - else - .nothing-here-block No preview for this file type + - if blob_text_viewable?(blob) + - if diff_view == 'parallel' + = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i + - else + = render "projects/diffs/text_file", diff_file: diff_file, index: i + - elsif blob.image? + - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file) + = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i + - else + .nothing-here-block No preview for this file type diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index faa2830c16..d2e85cabf7 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -24,6 +24,10 @@ module Gitlab @lines ||= parser.parse(raw_diff.each_line).to_a end + def too_large? + diff.too_large? + end + def highlighted_diff_lines Gitlab::Diff::Highlight.new(self).highlight end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 0d9694f2c1..a0cbef6e6a 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -18,4 +18,18 @@ describe Gitlab::Diff::File, lib: true do describe :mode_changed? do it { expect(diff_file.mode_changed?).to be_falsey } end + + describe '#too_large?' do + it 'returns true for a file that is too large' do + expect(diff).to receive(:too_large?).and_return(true) + + expect(diff_file.too_large?).to eq(true) + end + + it 'returns false for a file that is small enough' do + expect(diff).to receive(:too_large?).and_return(false) + + expect(diff_file.too_large?).to eq(false) + end + end end From 4885423feadb563a8474b8c01415527a28c8ef9b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 18 Mar 2016 11:36:48 +0000 Subject: [PATCH 109/153] Fixes issue with close button not working on MR Closes #14383 --- app/assets/javascripts/notes.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index b164231e7e..8253221658 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -627,10 +627,10 @@ class @Notes if closebtn.text() isnt closetext closebtn.text(closetext) - if reopenbtn.is(':not(.btn-comment-and-reopen)') + if reopenbtn.is('.btn-comment-and-reopen') reopenbtn.removeClass('btn-comment-and-reopen') - if closebtn.is(':not(.btn-comment-and-close)') + if closebtn.is('.btn-comment-and-close') closebtn.removeClass('btn-comment-and-close') if discardbtn.is(':visible') From 882ebfc29661f4e78aabbb63230ef9ae08770a36 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 15 Mar 2016 18:20:22 +0000 Subject: [PATCH 110/153] Changing filter dropdowns shows loading Instead of doing a full refresh of the page - i've modified the filterResults method on the Issues object to work for this form as well --- app/assets/javascripts/gl_dropdown.js.coffee | 6 +++++- app/assets/javascripts/issues.js.coffee | 16 +++++++++------- .../javascripts/labels_select.js.coffee | 19 ++++++++++++++----- .../javascripts/milestone_select.js.coffee | 8 +++++++- app/assets/javascripts/users_select.js.coffee | 8 +++++++- 5 files changed, 42 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 4f03847775..c81e8bf760 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -246,11 +246,15 @@ class GitLabDropdown if oldValue value = "#{oldValue},#{value}" else - @dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS + @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS # Toggle active class for the tick mark el.toggleClass "is-active" + # Toggle the dropdown label + if @options.toggleLabel + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject) + if value? if !field.length # Create hidden input for form diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index a0acf3028b..b6bc86f8af 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -41,18 +41,20 @@ @timer = null $("#issue_search").keyup -> clearTimeout(@timer) - @timer = setTimeout(Issues.filterResults, 500) + @timer = setTimeout( -> + Issues.filterResults $("#issue_search_form") + , 500) - filterResults: => - form = $("#issue_search_form") - search = $("#issue_search").val() + filterResults: (form) => $('.issues-holder').css("opacity", '0.5') - issues_url = form.attr('action') + '?' + form.serialize() + form_action = form.attr('action') + form_data = form.serialize() + issues_url = form_action + ("#{if form_action.indexOf("?") < 0 then '?' else '&'}") + form_data $.ajax type: "GET" - url: form.attr('action') - data: form.serialize() + url: form_action + data: form_data complete: -> $('.issues-holder').css("opacity", '1.0') success: (data) -> diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 5ade2cb66c..ed978f1bdb 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -10,6 +10,7 @@ class @LabelsSelect newColorField = $('#new_label_color') showNo = $(dropdown).data('show-no') showAny = $(dropdown).data('show-any') + defaultLabel = $(dropdown).text().trim() if newLabelField.length $('.suggest-colors-dropdown a').on "click", (e) -> @@ -51,13 +52,13 @@ class @LabelsSelect if showNo data.unshift( - id: "0" - title: 'No label' + id: 0 + title: 'No Label' ) if showAny data.unshift( - title: 'Any label' + title: 'Any Label' ) if data.length > 2 @@ -83,10 +84,18 @@ class @LabelsSelect search: fields: ['title'] selectable: true + toggleLabel: (selected) -> + if selected && selected.title isnt "Any Label" + selected.title + else + defaultLabel fieldName: $(dropdown).data('field-name') id: (label) -> - label.title + if label.title is "Any Label" + "" + else + label.title clicked: -> if $(dropdown).hasClass "js-filter-submit" - $(dropdown).parents('form').submit() + Issues.filterResults $(dropdown).parents("form") ) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 32159a7c17..f38c375c61 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -7,6 +7,7 @@ class @MilestoneSelect showNo = $(dropdown).data('show-no') showAny = $(dropdown).data('show-any') useId = $(dropdown).data('use-id') + defaultLabel = $(dropdown).text().trim() $(dropdown).glDropdown( data: (term, callback) -> @@ -42,6 +43,11 @@ class @MilestoneSelect search: fields: ['title'] selectable: true + toggleLabel: (selected) -> + if selected && selected.id + selected.title + else + defaultLabel fieldName: $(dropdown).data('field-name') text: (milestone) -> milestone.title @@ -57,5 +63,5 @@ class @MilestoneSelect milestone.title is selectedMilestone clicked: -> if $(dropdown).hasClass "js-filter-submit" - $(dropdown).parents('form').submit() + Issues.filterResults $(dropdown).parents("form") ) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 987c6f4b8d..46faae111c 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -10,6 +10,7 @@ class @UsersSelect showAnyUser = $(dropdown).data('any-user') firstUser = $(dropdown).data('first-user') selectedId = $(dropdown).data('selected') + defaultLabel = $(dropdown).text().trim() $(dropdown).glDropdown( data: (term, callback) => @@ -53,9 +54,14 @@ class @UsersSelect fields: ['name', 'username'] selectable: true fieldName: $(dropdown).data('field-name') + toggleLabel: (selected) -> + if selected && selected.id? + selected.name + else + defaultLabel clicked: -> if $(dropdown).hasClass "js-filter-submit" - $(dropdown).parents('form').submit() + Issues.filterResults $(dropdown).parents("form") renderRow: (user) -> username = if user.username then "@#{user.username}" else "" avatar = if user.avatar_url then user.avatar_url else false From 16d02de56ec0e52f8aaec7f9954de8497cfe9922 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 09:04:15 +0000 Subject: [PATCH 111/153] Fixed issue with dashboard issues not reloading --- app/assets/javascripts/labels_select.js.coffee | 9 +++++++-- app/assets/javascripts/milestone_select.js.coffee | 6 +++++- app/assets/javascripts/users_select.js.coffee | 6 +++++- spec/features/merge_requests/filter_by_milestone_spec.rb | 2 -- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index ed978f1bdb..269f2008e2 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -58,6 +58,7 @@ class @LabelsSelect if showAny data.unshift( + any: true title: 'Any Label' ) @@ -91,11 +92,15 @@ class @LabelsSelect defaultLabel fieldName: $(dropdown).data('field-name') id: (label) -> - if label.title is "Any Label" + if label.any? "" else label.title clicked: -> - if $(dropdown).hasClass "js-filter-submit" + page = $("body").data "page" + + if $(dropdown).hasClass("js-filter-submit") && page is "projects:issues:index" Issues.filterResults $(dropdown).parents("form") + else if $(dropdown).hasClass "js-filter-submit" + $(dropdown).parents("form").submit() ) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index f38c375c61..a4bf8654de 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -62,6 +62,10 @@ class @MilestoneSelect isSelected: (milestone) -> milestone.title is selectedMilestone clicked: -> - if $(dropdown).hasClass "js-filter-submit" + page = $("body").data "page" + + if $(dropdown).hasClass("js-filter-submit") && page is "projects:issues:index" Issues.filterResults $(dropdown).parents("form") + else if $(dropdown).hasClass "js-filter-submit" + $(dropdown).parents("form").submit() ) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 46faae111c..44ed31d04b 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -60,8 +60,12 @@ class @UsersSelect else defaultLabel clicked: -> - if $(dropdown).hasClass "js-filter-submit" + page = $("body").data "page" + + if $(dropdown).hasClass("js-filter-submit") && page is "projects:issues:index" Issues.filterResults $(dropdown).parents("form") + else if $(dropdown).hasClass "js-filter-submit" + $(dropdown).parents("form").submit() renderRow: (user) -> username = if user.username then "@#{user.username}" else "" avatar = if user.avatar_url then user.avatar_url else false diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index 1b2fd1bab1..b76e4c74c7 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -30,8 +30,6 @@ feature 'Merge Request filtering by Milestone', feature: true do def filter_by_milestone(title) find(".js-milestone-select").click - sleep 0.5 find(".milestone-filter a", text: title).click - sleep 1 end end From e8d52499db51021096d4b91596e4a207f4f5e62d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 18 Mar 2016 10:12:52 +0000 Subject: [PATCH 112/153] Fixed some CS styling issues Fixed bug where wouldn't work on MR index --- app/assets/javascripts/issues.js.coffee | 20 +++--- .../javascripts/labels_select.js.coffee | 63 ++++++++++--------- .../javascripts/milestone_select.js.coffee | 41 ++++++------ app/assets/javascripts/users_select.js.coffee | 31 ++++----- 4 files changed, 83 insertions(+), 72 deletions(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index b6bc86f8af..1127b28926 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -46,21 +46,23 @@ , 500) filterResults: (form) => - $('.issues-holder').css("opacity", '0.5') - form_action = form.attr('action') - form_data = form.serialize() - issues_url = form_action + ("#{if form_action.indexOf("?") < 0 then '?' else '&'}") + form_data + $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') + formAction = form.attr('action') + formData = form.serialize() + issuesUrl = formAction + issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}") + issuesUrl += formData $.ajax type: "GET" - url: form_action - data: form_data + url: formAction + data: formData complete: -> - $('.issues-holder').css("opacity", '1.0') + $('.issues-holder, .merge-requests-holder').css("opacity", '1.0') success: (data) -> - $('.issues-holder').html(data.html) + $('.issues-holder, .merge-requests-holder').html(data.html) # Change url so if user reload a page - search results are saved - history.replaceState {page: issues_url}, document.title, issues_url + history.replaceState {page: issuesUrl}, document.title, issuesUrl Issues.reload() dataType: "json" diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 269f2008e2..e6c1446f14 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -1,31 +1,32 @@ class @LabelsSelect constructor: -> $('.js-label-select').each (i, dropdown) -> - projectId = $(dropdown).data('project-id') - labelUrl = $(dropdown).data("labels") - selectedLabel = $(dropdown).data('selected') + $dropdown = $(dropdown) + projectId = $dropdown.data('project-id') + labelUrl = $dropdown.data('labels') + selectedLabel = $dropdown.data('selected') if selectedLabel - selectedLabel = selectedLabel.split(",") + selectedLabel = selectedLabel.split(',') newLabelField = $('#new_label_name') newColorField = $('#new_label_color') - showNo = $(dropdown).data('show-no') - showAny = $(dropdown).data('show-any') - defaultLabel = $(dropdown).text().trim() + showNo = $dropdown.data('show-no') + showAny = $dropdown.data('show-any') + defaultLabel = $dropdown.text().trim() if newLabelField.length - $('.suggest-colors-dropdown a').on "click", (e) -> + $('.suggest-colors-dropdown a').on 'click', (e) -> e.preventDefault() e.stopPropagation() - newColorField.val $(this).data("color") + newColorField.val $(this).data('color') $('.js-dropdown-label-color-preview') - .css 'background-color', $(this).data("color") + .css 'background-color', $(this).data('color') .addClass 'is-active' - $('.js-new-label-btn').on "click", (e) -> + $('.js-new-label-btn').on 'click', (e) -> e.preventDefault() e.stopPropagation() - if newLabelField.val() isnt "" && newColorField.val() isnt "" + if newLabelField.val() isnt '' and newColorField.val() isnt '' $('.js-new-label-btn').disable() # Create new label with API @@ -34,9 +35,9 @@ class @LabelsSelect color: newColorField.val() }, (label) -> $('.js-new-label-btn').enable() - $('.dropdown-menu-back', $(dropdown).parent()).trigger "click" + $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' - $(dropdown).glDropdown( + $dropdown.glDropdown( data: (term, callback) -> # We have to fetch the JS version of the labels list because there is no # public facing JSON url for labels @@ -58,23 +59,23 @@ class @LabelsSelect if showAny data.unshift( - any: true + isAny: true title: 'Any Label' ) if data.length > 2 - data.splice 2, 0, "divider" + data.splice 2, 0, 'divider' callback data renderRow: (label) -> if $.isArray(selectedLabel) - selected = "" + selected = '' $.each selectedLabel, (i, selectedLbl) -> selectedLbl = selectedLbl.trim() - if selected is "" && label.title is selectedLbl - selected = "is-active" + if selected is '' and label.title is selectedLbl + selected = 'is-active' else - selected = if label.title is selectedLabel then "is-active" else "" + selected = if label.title is selectedLabel then 'is-active' else '' "
  • @@ -86,21 +87,23 @@ class @LabelsSelect fields: ['title'] selectable: true toggleLabel: (selected) -> - if selected && selected.title isnt "Any Label" + if selected and selected.title isnt 'Any Label' selected.title else defaultLabel - fieldName: $(dropdown).data('field-name') + fieldName: $dropdown.data('field-name') id: (label) -> - if label.any? - "" + if label.isAny? + '' else label.title clicked: -> - page = $("body").data "page" - - if $(dropdown).hasClass("js-filter-submit") && page is "projects:issues:index" - Issues.filterResults $(dropdown).parents("form") - else if $(dropdown).hasClass "js-filter-submit" - $(dropdown).parents("form").submit() + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass 'js-filter-submit' + $dropdown.closest('form').submit() ) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index a4bf8654de..0287d98b1e 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -1,15 +1,16 @@ class @MilestoneSelect constructor: -> $('.js-milestone-select').each (i, dropdown) -> - projectId = $(dropdown).data('project-id') - milestonesUrl = $(dropdown).data('milestones') - selectedMilestone = $(dropdown).data('selected') - showNo = $(dropdown).data('show-no') - showAny = $(dropdown).data('show-any') - useId = $(dropdown).data('use-id') - defaultLabel = $(dropdown).text().trim() + $dropdown = $(dropdown) + projectId = $dropdown.data('project-id') + milestonesUrl = $dropdown.data('milestones') + selectedMilestone = $dropdown.data('selected') + showNo = $dropdown.data('show-no') + showAny = $dropdown.data('show-any') + useId = $dropdown.data('use-id') + defaultLabel = $dropdown.text().trim() - $(dropdown).glDropdown( + $dropdown.glDropdown( data: (term, callback) -> $.ajax( url: milestonesUrl @@ -17,7 +18,7 @@ class @MilestoneSelect html = $(data) data = [] html.find('.milestone strong a').each -> - link = $(@).attr("href").split("/") + link = $(@).attr('href').split('/') data.push( id: link[link.length - 1] title: $(@).text().trim() @@ -25,7 +26,7 @@ class @MilestoneSelect if showNo data.unshift( - id: "0" + id: '0' title: 'No Milestone' ) @@ -36,7 +37,7 @@ class @MilestoneSelect ) if data.length > 2 - data.splice 2, 0, "divider" + data.splice 2, 0, 'divider' callback(data) filterable: true @@ -44,11 +45,11 @@ class @MilestoneSelect fields: ['title'] selectable: true toggleLabel: (selected) -> - if selected && selected.id + if selected && 'id' of selected selected.title else defaultLabel - fieldName: $(dropdown).data('field-name') + fieldName: $dropdown.data('field-name') text: (milestone) -> milestone.title id: (milestone) -> @@ -56,16 +57,18 @@ class @MilestoneSelect if !milestone.isAny? milestone.title else - "" + '' else milestone.id isSelected: (milestone) -> milestone.title is selectedMilestone clicked: -> - page = $("body").data "page" + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' - if $(dropdown).hasClass("js-filter-submit") && page is "projects:issues:index" - Issues.filterResults $(dropdown).parents("form") - else if $(dropdown).hasClass "js-filter-submit" - $(dropdown).parents("form").submit() + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass 'js-filter-submit' + $dropdown.closest('form').submit() ) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 44ed31d04b..0e95014617 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -4,15 +4,16 @@ class @UsersSelect @userPath = "/autocomplete/users/:id.json" $('.js-user-search').each (i, dropdown) => - @projectId = $(dropdown).data('project-id') - @showCurrentUser = $(dropdown).data('current-user') - showNullUser = $(dropdown).data('null-user') - showAnyUser = $(dropdown).data('any-user') - firstUser = $(dropdown).data('first-user') - selectedId = $(dropdown).data('selected') - defaultLabel = $(dropdown).text().trim() + $dropdown = $(dropdown) + @projectId = $dropdown.data('project-id') + @showCurrentUser = $dropdown.data('current-user') + showNullUser = $dropdown.data('null-user') + showAnyUser = $dropdown.data('any-user') + firstUser = $dropdown.data('first-user') + selectedId = $dropdown.data('selected') + defaultLabel = $dropdown.text().trim() - $(dropdown).glDropdown( + $dropdown.glDropdown( data: (term, callback) => @users term, (users) => if term.length is 0 @@ -53,19 +54,21 @@ class @UsersSelect search: fields: ['name', 'username'] selectable: true - fieldName: $(dropdown).data('field-name') + fieldName: $dropdown.data('field-name') toggleLabel: (selected) -> if selected && selected.id? selected.name else defaultLabel clicked: -> - page = $("body").data "page" + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' - if $(dropdown).hasClass("js-filter-submit") && page is "projects:issues:index" - Issues.filterResults $(dropdown).parents("form") - else if $(dropdown).hasClass "js-filter-submit" - $(dropdown).parents("form").submit() + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass 'js-filter-submit' + $dropdown.closest('form').submit() renderRow: (user) -> username = if user.username then "@#{user.username}" else "" avatar = if user.avatar_url then user.avatar_url else false From c51af604e5bfdb33eb3d46ff2073066d10bce05d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 18 Mar 2016 10:14:39 +0000 Subject: [PATCH 113/153] Another CS style fix --- app/assets/javascripts/users_select.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 0e95014617..48831dd6bc 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -56,7 +56,7 @@ class @UsersSelect selectable: true fieldName: $dropdown.data('field-name') toggleLabel: (selected) -> - if selected && selected.id? + if selected && 'id' of selected selected.name else defaultLabel From af7850a8a3962455647755421aae3025bc0566de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 Mar 2016 12:54:37 +0100 Subject: [PATCH 114/153] Fix CHANGELOG by moving 8.5.5 items to 8.6 [ci skip] --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 102908102e..0f875ec29a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.6.0 (unreleased) - Add support for wiki with UTF-8 page names (Hiroyuki Sato) - Fix wiki search results point to raw source (Hiroyuki Sato) - Don't load all of GitLab in mail_room + - HTTP error pages work independently from location and config (Artem Sidorenko) - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - Memoize @group in Admin::GroupsController (Yatish Mehta) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) @@ -41,6 +42,7 @@ v 8.6.0 (unreleased) - Add shortcut to toggle markdown preview (Florent Baldino) - Show labels in dashboard and group milestone views - Add main language of a project in the list of projects (Tiago Botelho) + - Add #upcoming filter to Milestone filter (Tiago Botelho) - Add ability to show archived projects on dashboard, explore and group pages - Move group activity to separate page - Create external users which are excluded of internal and private projects unless access was explicitly granted @@ -62,8 +64,6 @@ v 8.5.5 - Prevent a 500 error in Todos when author was removed - Fix pagination for filtered dashboard and explore pages - Fix "Show all" link behavior - - Add #upcoming filter to Milestone filter (Tiago Botelho) - - HTTP error pages work independently from location and config (Artem Sidorenko) v 8.5.4 - Do not cache requests for badges (including builds badge) From e1ce7a47faeea46f10b2649e6fb379d85ec6de8a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Mar 2016 14:35:04 +0200 Subject: [PATCH 115/153] Add note in rest of update guides [ci skip] --- doc/update/8.3-to-8.4.md | 9 +++++++++ doc/update/8.4-to-8.5.md | 9 +++++++++ doc/update/8.5-to-8.6.md | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md index 269deec7a9..9f6517d948 100644 --- a/doc/update/8.3-to-8.4.md +++ b/doc/update/8.3-to-8.4.md @@ -1,5 +1,14 @@ # From 8.3 to 8.4 +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + ### 1. Stop server sudo service gitlab stop diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md index 0a9cb5683e..0cb137a03c 100644 --- a/doc/update/8.4-to-8.5.md +++ b/doc/update/8.4-to-8.5.md @@ -1,5 +1,14 @@ # From 8.4 to 8.5 +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + ### 1. Stop server sudo service gitlab stop diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md index 024f6e8a43..7d63915af5 100644 --- a/doc/update/8.5-to-8.6.md +++ b/doc/update/8.5-to-8.6.md @@ -1,5 +1,14 @@ # From 8.5 to 8.6 +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + ### 1. Stop server sudo service gitlab stop From 195ef985f3271e5c64010f192bfe5ee1c78e2ba8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 18 Mar 2016 12:42:30 +0000 Subject: [PATCH 116/153] Added test --- features/project/merge_requests.feature | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 74685d24a7..823658b4f2 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -325,3 +325,11 @@ Feature: Project Merge Requests When I click the "Target branch" dropdown And I select a new target branch Then I should see new target branch changes + + @javascript + Scenario: I can close merge request after commenting + Given I visit merge request page "Bug NS-04" + And I leave a comment like "XML attached" + Then I should see comment "XML attached" + And I click link "Close" + Then I should see closed merge request "Bug NS-04" From 42834095bc92aa2ebe2113a5655227ff0e2799e9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 16 Mar 2016 16:31:37 -0300 Subject: [PATCH 117/153] Change target_id to null on todos --- .../20160316192622_change_target_id_to_null_on_todos.rb | 5 +++++ db/schema.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160316192622_change_target_id_to_null_on_todos.rb diff --git a/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb b/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb new file mode 100644 index 0000000000..6871b3920d --- /dev/null +++ b/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb @@ -0,0 +1,5 @@ +class ChangeTargetIdToNullOnTodos < ActiveRecord::Migration + def change + change_column_null :todos, :target_id, true + end +end diff --git a/db/schema.rb b/db/schema.rb index 7e6863ef47..cbec180844 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -867,7 +867,7 @@ ActiveRecord::Schema.define(version: 20160316204731) do create_table "todos", force: :cascade do |t| t.integer "user_id", null: false t.integer "project_id", null: false - t.integer "target_id", null: false + t.integer "target_id" t.string "target_type", null: false t.integer "author_id" t.integer "action", null: false From 1e76245d4e8c5ecbd915c517b6c8923e9c4bbd2d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 16 Mar 2016 17:49:20 -0300 Subject: [PATCH 118/153] Add commit_id to todos --- db/migrate/20160316204731_add_commit_id_to_todos.rb | 6 ++++++ db/schema.rb | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 db/migrate/20160316204731_add_commit_id_to_todos.rb diff --git a/db/migrate/20160316204731_add_commit_id_to_todos.rb b/db/migrate/20160316204731_add_commit_id_to_todos.rb new file mode 100644 index 0000000000..ae19fdd1ab --- /dev/null +++ b/db/migrate/20160316204731_add_commit_id_to_todos.rb @@ -0,0 +1,6 @@ +class AddCommitIdToTodos < ActiveRecord::Migration + def change + add_column :todos, :commit_id, :string + add_index :todos, :commit_id + end +end diff --git a/db/schema.rb b/db/schema.rb index cbec180844..5b2f5aa3dd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -875,9 +875,11 @@ ActiveRecord::Schema.define(version: 20160316204731) do t.datetime "created_at" t.datetime "updated_at" t.integer "note_id" + t.string "commit_id" end add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree + add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree add_index "todos", ["state"], name: "index_todos_on_state", using: :btree From c29da3f8ca1d759b8cbecb91b6e0529b18cc5c85 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 16 Mar 2016 20:31:30 -0300 Subject: [PATCH 119/153] Trigger a todo for mentions on commits page --- app/helpers/todos_helper.rb | 11 +++-- app/models/todo.rb | 29 ++++++++++++- app/services/todo_service.rb | 65 ++++++++++++++++++------------ spec/factories/todos.rb | 5 +++ spec/models/todo_spec.rb | 62 +++++++++++++++++++++++++++- spec/services/todo_service_spec.rb | 9 ++++- 6 files changed, 149 insertions(+), 32 deletions(-) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 07ddc691d8..9604a4e01b 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -16,14 +16,19 @@ module TodosHelper def todo_target_link(todo) target = todo.target_type.titleize.downcase - link_to "#{target} #{todo.target.to_reference}", todo_target_path(todo), { title: h(todo.target.title) } + link_to "#{target} #{todo.to_reference}", todo_target_path(todo), { title: h(todo.target.title) } end def todo_target_path(todo) anchor = dom_id(todo.note) if todo.note.present? - polymorphic_path([todo.project.namespace.becomes(Namespace), - todo.project, todo.target], anchor: anchor) + if todo.for_commit? + namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project, + todo.target, anchor: anchor) + else + polymorphic_path([todo.project.namespace.becomes(Namespace), + todo.project, todo.target], anchor: anchor) + end end def todos_filter_params diff --git a/app/models/todo.rb b/app/models/todo.rb index 5f91991f78..b00a1b3dc7 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -27,7 +27,9 @@ class Todo < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true, allow_nil: true - validates :action, :project, :target, :user, presence: true + validates :action, :project, :target_type, :user, presence: true + validates :target_id, presence: true, if: ->(t) { t.target_type.present? && t.target_type != 'Commit' } + validates :commit_id, presence: true, if: ->(t) { t.target_type.present? && t.target_type == 'Commit' } default_scope { reorder(id: :desc) } @@ -50,4 +52,29 @@ class Todo < ActiveRecord::Base target.title end end + + def for_commit? + target_type == "Commit" + end + + # override to return commits, which are not active record + def target + if for_commit? + project.commit(commit_id) + else + super + end + # Temp fix to prevent app crash + # if note commit id doesn't exist + rescue + nil + end + + def to_reference + if for_commit? + Commit.truncate_sha(commit_id) + else + target.to_reference + end + end end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 4392e2d17f..1027c30912 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -103,24 +103,16 @@ class TodoService # * mark all pending todos related to the target for the current user as done # def mark_pending_todos_as_done(target, user) - pending_todos(user, target.project, target).update_all(state: :done) + attributes = attributes_for_target(target) + pending_todos(user, attributes).update_all(state: :done) end private - def create_todos(project, target, author, users, action, note = nil) + def create_todos(users, attributes) Array(users).each do |user| - next if pending_todos(user, project, target).exists? - - Todo.create( - project: project, - user_id: user.id, - author_id: author.id, - target_id: target.id, - target_type: target.class.name, - action: action, - note: note - ) + next if pending_todos(user, attributes).exists? + Todo.create(attributes.merge(user_id: user.id)) end end @@ -130,8 +122,8 @@ class TodoService end def handle_note(note, author) - # Skip system notes, notes on commit, and notes on project snippet - return if note.system? || ['Commit', 'Snippet'].include?(note.noteable_type) + # Skip system notes, and notes on project snippet + return if note.system? || ['Snippet'].include?(note.noteable_type) project = note.project target = note.noteable @@ -142,13 +134,39 @@ class TodoService def create_assignment_todo(issuable, author) if issuable.assignee && issuable.assignee != author - create_todos(issuable.project, issuable, author, issuable.assignee, Todo::ASSIGNED) + attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED) + create_todos(issuable.assignee, attributes) end end - def create_mention_todos(project, issuable, author, note = nil) - mentioned_users = filter_mentioned_users(project, note || issuable, author) - create_todos(project, issuable, author, mentioned_users, Todo::MENTIONED, note) + def create_mention_todos(project, target, author, note = nil) + mentioned_users = filter_mentioned_users(project, note || target, author) + attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note) + create_todos(mentioned_users, attributes) + end + + def attributes_for_target(target) + attributes = { + project_id: target.project.id, + target_id: target.id, + target_type: target.class.name, + commit_id: nil + } + + if target.is_a?(Commit) + attributes.merge!(target_id: nil, commit_id: target.id) + end + + attributes + end + + def attributes_for_todo(project, target, author, action, note = nil) + attributes_for_target(target).merge!( + project_id: project.id, + author_id: author.id, + action: action, + note: note + ) end def filter_mentioned_users(project, target, author) @@ -160,11 +178,8 @@ class TodoService mentioned_users.uniq end - def pending_todos(user, project, target) - user.todos.pending.where( - project_id: project.id, - target_id: target.id, - target_type: target.class.name - ) + def pending_todos(user, criteria = {}) + valid_keys = [:project_id, :target_id, :target_type, :commit_id] + user.todos.pending.where(criteria.slice(*valid_keys)) end end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index bd85b1d798..b484315ed1 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -30,5 +30,10 @@ FactoryGirl.define do trait :mentioned do action { Todo::MENTIONED } end + + trait :on_commit do + commit_id RepoHelpers.sample_commit.id + target_type "Commit" + end end end diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index fe9ea7e7d1..3e59443589 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -18,6 +18,10 @@ require 'spec_helper' describe Todo, models: true do + let(:project) { create(:project) } + let(:commit) { project.commit } + let(:issue) { create(:issue) } + describe 'relationships' do it { is_expected.to belong_to(:author).class_name("User") } it { is_expected.to belong_to(:note) } @@ -33,8 +37,22 @@ describe Todo, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:action) } - it { is_expected.to validate_presence_of(:target) } + it { is_expected.to validate_presence_of(:target_type) } it { is_expected.to validate_presence_of(:user) } + + context 'for commits' do + subject { described_class.new(target_type: 'Commit') } + + it { is_expected.to validate_presence_of(:commit_id) } + it { is_expected.not_to validate_presence_of(:target_id) } + end + + context 'for issuables' do + subject { described_class.new(target: issue) } + + it { is_expected.to validate_presence_of(:target_id) } + it { is_expected.not_to validate_presence_of(:commit_id) } + end end describe '#body' do @@ -66,4 +84,46 @@ describe Todo, models: true do expect { todo.done! }.not_to raise_error end end + + describe '#for_commit?' do + it 'returns true when target is a commit' do + subject.target_type = 'Commit' + expect(subject.for_commit?).to eq true + end + + it 'returns false when target is an issuable' do + subject.target_type = 'Issue' + expect(subject.for_commit?).to eq false + end + end + + describe '#target' do + it 'returns an instance of Commit for commits' do + subject.project = project + subject.target_type = 'Commit' + subject.commit_id = commit.id + + expect(subject.target).to be_a(Commit) + expect(subject.target).to eq commit + end + + it 'returns the issuable for issuables' do + subject.target_id = issue.id + subject.target_type = issue.class.name + expect(subject.target).to eq issue + end + end + + describe '#to_reference' do + it 'returns the short commit id for commits' do + subject.target_type = 'Commit' + subject.commit_id = commit.id + expect(subject.to_reference).to eq Commit.truncate_sha(commit.id) + end + + it 'returns reference for issuables' do + subject.target = issue + expect(subject.to_reference).to eq issue.to_reference + end + end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 96420acb31..b4728807b8 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -148,8 +148,13 @@ describe TodoService, services: true do should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) end - it 'does not create todo when leaving a note on commit' do - should_not_create_any_todo { service.new_note(note_on_commit, john_doe) } + it 'creates a todo for each valid mentioned user when leaving a note on commit' do + service.new_note(note_on_commit, john_doe) + + should_create_todo(user: michael, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_not_create_todo(user: stranger, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) end it 'does not create todo when leaving a note on snippet' do From fb72271e24663c74e6dd66e610fd1add98628648 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 16 Mar 2016 20:56:23 -0300 Subject: [PATCH 120/153] Use todo.done without ! in the controller to mark todo as done --- app/controllers/dashboard/todos_controller.rb | 4 ++-- app/models/todo.rb | 2 +- spec/models/todo_spec.rb | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 7857af9c5d..be488483b0 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -6,7 +6,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def destroy - todo.done! + todo.done todo_notice = 'Todo was successfully marked as done.' @@ -20,7 +20,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def destroy_all - @todos.each(&:done!) + @todos.each(&:done) respond_to do |format| format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } diff --git a/app/models/todo.rb b/app/models/todo.rb index b00a1b3dc7..68263a64d5 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -38,7 +38,7 @@ class Todo < ActiveRecord::Base state_machine :state, initial: :pending do event :done do - transition [:pending, :done] => :done + transition [:pending] => :done end state :pending diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 3e59443589..7618286302 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -73,15 +73,15 @@ describe Todo, models: true do end end - describe '#done!' do + describe '#done' do it 'changes state to done' do todo = create(:todo, state: :pending) - expect { todo.done! }.to change(todo, :state).from('pending').to('done') + expect { todo.done }.to change(todo, :state).from('pending').to('done') end it 'does not raise error when is already done' do todo = create(:todo, state: :done) - expect { todo.done! }.not_to raise_error + expect { todo.done }.not_to raise_error end end From a5bad46505d152fe824fb9c51cb15a2a45caff14 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 16 Mar 2016 21:02:20 -0300 Subject: [PATCH 121/153] Update schema info comment on todo related files --- app/models/todo.rb | 5 +++-- spec/factories/todos.rb | 5 +++-- spec/models/todo_spec.rb | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index 68263a64d5..024cdcc0d8 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -5,14 +5,15 @@ # id :integer not null, primary key # user_id :integer not null # project_id :integer not null -# target_id :integer not null +# target_id :integer # target_type :string not null # author_id :integer -# note_id :integer # action :integer not null # state :string not null # created_at :datetime # updated_at :datetime +# note_id :integer +# commit_id :string # class Todo < ActiveRecord::Base diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index b484315ed1..7ae06c2784 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -5,14 +5,15 @@ # id :integer not null, primary key # user_id :integer not null # project_id :integer not null -# target_id :integer not null +# target_id :integer # target_type :string not null # author_id :integer -# note_id :integer # action :integer not null # state :string not null # created_at :datetime # updated_at :datetime +# note_id :integer +# commit_id :string # FactoryGirl.define do diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 7618286302..ad74421642 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -5,14 +5,15 @@ # id :integer not null, primary key # user_id :integer not null # project_id :integer not null -# target_id :integer not null +# target_id :integer # target_type :string not null # author_id :integer -# note_id :integer # action :integer not null # state :string not null # created_at :datetime # updated_at :datetime +# note_id :integer +# commit_id :string # require 'spec_helper' From 2eeeb266e36abdbe78be8f71081bb19e83175819 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 18 Mar 2016 10:27:26 -0300 Subject: [PATCH 122/153] Reuse `for_commit?` on conditional validations --- app/models/todo.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index 024cdcc0d8..4be5806b8d 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -29,8 +29,8 @@ class Todo < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true, allow_nil: true validates :action, :project, :target_type, :user, presence: true - validates :target_id, presence: true, if: ->(t) { t.target_type.present? && t.target_type != 'Commit' } - validates :commit_id, presence: true, if: ->(t) { t.target_type.present? && t.target_type == 'Commit' } + validates :target_id, presence: true, unless: :for_commit? + validates :commit_id, presence: true, if: :for_commit? default_scope { reorder(id: :desc) } From 231d4fb9f8a30e97fd9bcb7176ad8337557f6ea0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 18 Mar 2016 10:32:22 -0300 Subject: [PATCH 123/153] Use `Commit#short_id` instead of `Commit.truncate_sha` --- app/models/todo.rb | 2 +- spec/models/todo_spec.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index 4be5806b8d..b135407a8e 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -73,7 +73,7 @@ class Todo < ActiveRecord::Base def to_reference if for_commit? - Commit.truncate_sha(commit_id) + target.short_id else target.to_reference end diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index ad74421642..0fbd36f3a2 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -117,9 +117,11 @@ describe Todo, models: true do describe '#to_reference' do it 'returns the short commit id for commits' do + subject.project = project subject.target_type = 'Commit' subject.commit_id = commit.id - expect(subject.to_reference).to eq Commit.truncate_sha(commit.id) + + expect(subject.to_reference).to eq commit.short_id end it 'returns reference for issuables' do From 71cc3caa85e8c04a397d243ffa96c20842414a11 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 18 Mar 2016 10:34:23 -0300 Subject: [PATCH 124/153] Use `Note#for_project_snippet?` to skip notes on project snippet --- app/services/todo_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 1027c30912..f2662922e9 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -123,7 +123,7 @@ class TodoService def handle_note(note, author) # Skip system notes, and notes on project snippet - return if note.system? || ['Snippet'].include?(note.noteable_type) + return if note.system? || note.for_project_snippet? project = note.project target = note.noteable From 05975e96f98521a219788a0bec3d6f71458833e1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 16 Mar 2016 21:02:51 -0300 Subject: [PATCH 125/153] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 74217e80bf..28c1c30e20 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -51,6 +51,7 @@ v 8.6.0 (unreleased) - Continue parameters are checked to ensure redirection goes to the same instance - User deletion is now done in the background so the request can not time out - Canceled builds are now ignored in compound build status if marked as `allowed to fail` + - Trigger a todo for mentions on commits page v 8.5.8 - Bump Git version requirement to 2.7.4 From 0858cb097afcaa529e5e0fe7154af53f66ce62fe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 18:08:35 +0000 Subject: [PATCH 126/153] Fixed issue with labels dropdown getting wrong labels --- app/assets/javascripts/labels_select.js.coffee | 9 --------- app/assets/javascripts/milestone_select.js.coffee | 9 --------- app/controllers/projects/labels_controller.rb | 7 +++++++ app/controllers/projects/milestones_controller.rb | 8 ++++++++ app/views/shared/issuable/_filter.html.haml | 4 ++-- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index e6c1446f14..4f03c36775 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -39,18 +39,9 @@ class @LabelsSelect $dropdown.glDropdown( data: (term, callback) -> - # We have to fetch the JS version of the labels list because there is no - # public facing JSON url for labels $.ajax( url: labelUrl ).done (data) -> - html = $(data) - data = [] - html.find('.label-row a').each -> - data.push( - title: $(@).text().trim() - ) - if showNo data.unshift( id: 0 diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 0287d98b1e..5c35107ae6 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -15,15 +15,6 @@ class @MilestoneSelect $.ajax( url: milestonesUrl ).done (data) -> - html = $(data) - data = [] - html.find('.milestone strong a').each -> - link = $(@).attr('href').split('/') - data.push( - id: link[link.length - 1] - title: $(@).text().trim() - ) - if showNo data.unshift( id: '0' diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 40d8098690..c61793f89a 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -12,6 +12,13 @@ class Projects::LabelsController < Projects::ApplicationController def index @labels = @project.labels.page(params[:page]).per(PER_PAGE) + + respond_to do |format| + format.html + format.json do + render json: @labels.to_json + end + end end def new diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index da46731d94..cfb0435717 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -19,7 +19,15 @@ class Projects::MilestonesController < Projects::ApplicationController end @milestones = @milestones.includes(:project) + @milestones_all = @milestones @milestones = @milestones.page(params[:page]).per(PER_PAGE) + + respond_to do |format| + format.html + format.json do + render json: @milestones_all.to_json + end + end end def new diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index dfdc84ba4c..318691ac7c 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -22,7 +22,7 @@ - if params[:milestone_title] = hidden_field_tag(:milestone_title, params[:milestone_title]) = dropdown_tag(h(params[:milestone_name] || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :js) if @project) } }) do + placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :json) if @project) } }) do - if @project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, @project @@ -40,7 +40,7 @@ - if params[:label_name] = hidden_field_tag(:label_name, params[:label_name]) .dropdown - %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :js) if @project)}} + %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text = h(params[:label_name] || "Label") = icon('chevron-down') From 645b7a0a3389dca3a709f65a52ae765f213078f2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 18:37:25 +0000 Subject: [PATCH 127/153] Full labels data in JSON --- app/controllers/projects/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index c61793f89a..5f471d405f 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -16,7 +16,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @labels.to_json + render json: @project.labels end end end From d847db79cb6a99d9cfb6f4c57587887942965388 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 16 Mar 2016 19:14:31 +0000 Subject: [PATCH 128/153] Fixes issue on dashboard issues They would try to load JSON from a project even though it isn't a single project --- app/controllers/dashboard_controller.rb | 18 ++++++ app/helpers/labels_helper.rb | 12 ++-- app/helpers/milestones_helper.rb | 13 ++-- app/views/shared/issuable/_filter.html.haml | 59 +------------------ .../shared/issuable/_label_dropdown.html.haml | 39 ++++++++++++ .../issuable/_milestone_dropdown.html.haml | 16 +++++ config/routes.rb | 2 + 7 files changed, 92 insertions(+), 67 deletions(-) create mode 100644 app/views/shared/issuable/_label_dropdown.html.haml create mode 100644 app/views/shared/issuable/_milestone_dropdown.html.haml diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 139e40db18..614e28b6dc 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -20,6 +20,24 @@ class DashboardController < Dashboard::ApplicationController end end + def labels + respond_to do |format| + format.json do + projects + render json: view_context.projects_labels_options + end + end + end + + def milestones + respond_to do |format| + format.json do + projects + render json: view_context.projects_milestones_options + end + end + end + protected def load_events diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 4455dcd0e2..361811163f 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -116,12 +116,14 @@ module LabelsHelper else Label.where(project_id: @projects) end + end - grouped_labels = GlobalLabel.build_collection(labels) - grouped_labels.unshift(Label::None) - grouped_labels.unshift(Label::Any) - - options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name]) + def labels_filter_path + if @project + namespace_project_labels_path(@project.namespace, @project, :json) + else + labels_dashboard_path(:json) + end end def label_subscription_status(label) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 92ed0891e9..de60be4eb2 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -56,12 +56,15 @@ module MilestonesHelper epoch = DateTime.parse('1970-01-01') grouped_milestones = GlobalMilestone.build_collection(milestones) - grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } - grouped_milestones.unshift(Milestone::None) - grouped_milestones.unshift(Milestone::Any) - grouped_milestones.unshift(Milestone::Upcoming) + grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } + end - options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title]) + def milestones_filter_dropdown_path + if @project + namespace_project_milestones_path(@project.namespace, @project, :json) + else + milestones_dashboard_path(:json) + end end def milestone_remaining_days(milestone) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 318691ac7c..cb25d332ff 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -19,65 +19,10 @@ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } }) .filter-item.inline.milestone-filter - - if params[:milestone_title] - = hidden_field_tag(:milestone_title, params[:milestone_title]) - = dropdown_tag(h(params[:milestone_name] || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :json) if @project) } }) do - - if @project - %ul.dropdown-footer-list - - if can? current_user, :admin_milestone, @project - %li - = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do - Create new - %li - = link_to namespace_project_milestones_path(@project.namespace, @project) do - - if can? current_user, :admin_milestone, @project - Manage milestones - - else - View milestones + = render "shared/issuable/milestone_dropdown" .filter-item.inline.labels-filter - - if params[:label_name] - = hidden_field_tag(:label_name, params[:label_name]) - .dropdown - %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} - %span.dropdown-toggle-text - = h(params[:label_name] || "Label") - = icon('chevron-down') - .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - .dropdown-page-one - = dropdown_title("Filter by label") - = dropdown_filter("Search labels") - = dropdown_content - - if @project - = dropdown_footer do - %ul.dropdown-footer-list - - if can? current_user, :admin_label, @project - %li - %a.dropdown-toggle-page{href: "#"} - Create new - %li - = link_to namespace_project_labels_path(@project.namespace, @project) do - - if can? current_user, :admin_label, @project - Manage labels - - else - View labels - - if can? current_user, :admin_label, @project - .dropdown-page-two - = dropdown_title("Create new label", back: true) - = dropdown_content do - %input#new_label_color{type: "hidden"} - %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} - .dropdown-label-color-preview.js-dropdown-label-color-preview - .suggest-colors.suggest-colors-dropdown - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - %button.btn.btn-primary.js-new-label-btn{type: "button"} - Create - = dropdown_loading - .dropdown-loading - = icon('spinner spin') + = render "shared/issuable/label_dropdown" .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml new file mode 100644 index 0000000000..cb490e5b75 --- /dev/null +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -0,0 +1,39 @@ +- if params[:label_name] + = hidden_field_tag(:label_name, params[:label_name]) +.dropdown + %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: labels_filter_path}} + %span.dropdown-toggle-text + = h(params[:label_name] || "Label") + = icon('chevron-down') + .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable + .dropdown-page-one + = dropdown_title("Filter by label") + = dropdown_filter("Search labels") + = dropdown_content + - if @project + = dropdown_footer do + %ul.dropdown-footer-list + - if can? current_user, :admin_label, @project + %li + %a.dropdown-toggle-page{href: "#"} + Create new + %li + = link_to namespace_project_labels_path(@project.namespace, @project) do + - if can? current_user, :admin_label, @project + Manage labels + - else + View labels + - if can? current_user, :admin_label, @project and @project + .dropdown-page-two + = dropdown_title("Create new label", back: true) + = dropdown_content do + %input#new_label_color{type: "hidden"} + %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} + .dropdown-label-color-preview.js-dropdown-label-color-preview + .suggest-colors.suggest-colors-dropdown + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + %button.btn.btn-primary.js-new-label-btn{type: "button"} + Create + = dropdown_loading diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml new file mode 100644 index 0000000000..640b23c401 --- /dev/null +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -0,0 +1,16 @@ +- if params[:milestone_title] + = hidden_field_tag(:milestone_title, params[:milestone_title]) += dropdown_tag(h(params[:milestone_name] || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", + placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: milestones_filter_dropdown_path } }) do + - if @project + %ul.dropdown-footer-list + - if can? current_user, :admin_milestone, @project + %li + = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do + Create new + %li + = link_to namespace_project_milestones_path(@project.namespace, @project) do + - if can? current_user, :admin_milestone, @project + Manage milestones + - else + View milestones diff --git a/config/routes.rb b/config/routes.rb index 2ae282f48a..ec79522002 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -351,6 +351,8 @@ Rails.application.routes.draw do get :issues get :merge_requests get :activity + get :labels + get :milestones scope module: :dashboard do resources :milestones, only: [:index, :show] From 0eecc214853fc07b9237b50ab65a6c702cbfaeb4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 Mar 2016 09:09:06 +0000 Subject: [PATCH 129/153] Updated controller with before_action Fixed other issues based on feedback --- app/controllers/dashboard_controller.rb | 4 +--- app/controllers/projects/milestones_controller.rb | 8 ++++---- app/helpers/dropdowns_helper.rb | 2 +- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 614e28b6dc..36986d9a18 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController include MergeRequestsAction before_action :event_filter, only: :activity - before_action :projects, only: [:issues, :merge_requests] + before_action :projects, only: [:issues, :merge_requests, :labels, :milestones] respond_to :html @@ -23,7 +23,6 @@ class DashboardController < Dashboard::ApplicationController def labels respond_to do |format| format.json do - projects render json: view_context.projects_labels_options end end @@ -32,7 +31,6 @@ class DashboardController < Dashboard::ApplicationController def milestones respond_to do |format| format.json do - projects render json: view_context.projects_milestones_options end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index cfb0435717..7b70f5fa7d 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -19,13 +19,13 @@ class Projects::MilestonesController < Projects::ApplicationController end @milestones = @milestones.includes(:project) - @milestones_all = @milestones - @milestones = @milestones.page(params[:page]).per(PER_PAGE) respond_to do |format| - format.html + format.html do + @milestones = @milestones.page(params[:page]).per(PER_PAGE) + end format.json do - render json: @milestones_all.to_json + render json: @milestones_all end end end diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 74f326e0b8..ceff1fbb16 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -24,7 +24,7 @@ module DropdownsHelper capture(&block) if block && !options.has_key?(:footer_content) end - if block && options.has_key?(:footer_content) + if block && options[:footer_content] output << content_tag(:div, class: "dropdown-footer") do capture(&block) end diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index cb490e5b75..8399c8fba1 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,7 +1,7 @@ - if params[:label_name] = hidden_field_tag(:label_name, params[:label_name]) .dropdown - %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: labels_filter_path}} + %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path}} %span.dropdown-toggle-text = h(params[:label_name] || "Label") = icon('chevron-down') diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 640b23c401..6e5abdeb66 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,7 +1,7 @@ - if params[:milestone_title] = hidden_field_tag(:milestone_title, params[:milestone_title]) = dropdown_tag(h(params[:milestone_name] || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: milestones_filter_dropdown_path } }) do + placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path } }) do - if @project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, @project From a45cb688095eda294b0242a09d28168d4e279421 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 Mar 2016 10:40:54 +0000 Subject: [PATCH 130/153] Fixed failing tests --- app/controllers/projects/milestones_controller.rb | 2 +- app/helpers/labels_helper.rb | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 7b70f5fa7d..0998b191c0 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -25,7 +25,7 @@ class Projects::MilestonesController < Projects::ApplicationController @milestones = @milestones.page(params[:page]).per(PER_PAGE) end format.json do - render json: @milestones_all + render json: @milestones end end end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 361811163f..49f701e9be 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -110,12 +110,11 @@ module LabelsHelper end def projects_labels_options - labels = - if @project - @project.labels - else - Label.where(project_id: @projects) - end + if @project + @project.labels + else + Label.where(project_id: @projects) + end end def labels_filter_path From 2a8858ca8adbc54d7e24e698fa8ce370a1e91157 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 18 Mar 2016 13:24:47 -0300 Subject: [PATCH 131/153] Rename `Todo#to_reference` to `Todo#target_reference` Since we're not actually returning a todo reference. --- app/helpers/todos_helper.rb | 2 +- app/models/todo.rb | 2 +- spec/models/todo_spec.rb | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 9604a4e01b..edc5686cf0 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -16,7 +16,7 @@ module TodosHelper def todo_target_link(todo) target = todo.target_type.titleize.downcase - link_to "#{target} #{todo.to_reference}", todo_target_path(todo), { title: h(todo.target.title) } + link_to "#{target} #{todo.target_reference}", todo_target_path(todo), { title: todo.target.title } end def todo_target_path(todo) diff --git a/app/models/todo.rb b/app/models/todo.rb index b135407a8e..ab804d712a 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -71,7 +71,7 @@ class Todo < ActiveRecord::Base nil end - def to_reference + def target_reference if for_commit? target.short_id else diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 0fbd36f3a2..22acfdfe2c 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -115,18 +115,18 @@ describe Todo, models: true do end end - describe '#to_reference' do + describe '#target_reference' do it 'returns the short commit id for commits' do subject.project = project subject.target_type = 'Commit' subject.commit_id = commit.id - expect(subject.to_reference).to eq commit.short_id + expect(subject.target_reference).to eq commit.short_id end it 'returns reference for issuables' do subject.target = issue - expect(subject.to_reference).to eq issue.to_reference + expect(subject.target_reference).to eq issue.to_reference end end end From 2f210adee260a42bbecca5e099bb2e0cf18aacc6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 18 Mar 2016 12:21:07 +0000 Subject: [PATCH 132/153] Fixes issue with filter label missing on labels & milestones --- app/assets/javascripts/labels_select.js.coffee | 2 +- app/assets/javascripts/milestone_select.js.coffee | 2 +- app/assets/javascripts/users_select.js.coffee | 2 +- app/views/shared/issuable/_filter.html.haml | 4 ++-- app/views/shared/issuable/_label_dropdown.html.haml | 4 ++-- app/views/shared/issuable/_milestone_dropdown.html.haml | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 4f03c36775..4a0c18a99a 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -11,7 +11,7 @@ class @LabelsSelect newColorField = $('#new_label_color') showNo = $dropdown.data('show-no') showAny = $dropdown.data('show-any') - defaultLabel = $dropdown.text().trim() + defaultLabel = $dropdown.data('default-label') if newLabelField.length $('.suggest-colors-dropdown a').on 'click', (e) -> diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 5c35107ae6..e17a1adb64 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -8,7 +8,7 @@ class @MilestoneSelect showNo = $dropdown.data('show-no') showAny = $dropdown.data('show-any') useId = $dropdown.data('use-id') - defaultLabel = $dropdown.text().trim() + defaultLabel = $dropdown.data('default-label') $dropdown.glDropdown( data: (term, callback) -> diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 48831dd6bc..3d6452d2f4 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -11,7 +11,7 @@ class @UsersSelect showAnyUser = $dropdown.data('any-user') firstUser = $dropdown.data('first-user') selectedId = $dropdown.data('selected') - defaultLabel = $dropdown.text().trim() + defaultLabel = $dropdown.data('default-label') $dropdown.glDropdown( data: (term, callback) => diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index cb25d332ff..ac20f7d1f7 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -10,13 +10,13 @@ - if params[:author_id] = hidden_field_tag(:author_id, params[:author_id]) = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author", - placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id" } }) + placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) .filter-item.inline - if params[:assignee_id] = hidden_field_tag(:assignee_id, params[:assignee_id]) = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee", - placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } }) + placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter = render "shared/issuable/milestone_dropdown" diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 8399c8fba1..8761731518 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,9 +1,9 @@ - if params[:label_name] = hidden_field_tag(:label_name, params[:label_name]) .dropdown - %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path}} + %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %span.dropdown-toggle-text - = h(params[:label_name] || "Label") + = h(params[:label_name].presence || "Label") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable .dropdown-page-one diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 6e5abdeb66..0434506c8d 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,7 +1,7 @@ - if params[:milestone_title] = hidden_field_tag(:milestone_title, params[:milestone_title]) -= dropdown_tag(h(params[:milestone_name] || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path } }) do += dropdown_tag(h(params[:milestone_title].presence || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", + placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if @project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, @project From 02b0c37cabf0456a4c36680fb1313b1107f35f54 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 18 Mar 2016 13:27:27 -0300 Subject: [PATCH 133/153] Refactor `Todo#target` --- app/models/todo.rb | 6 +----- spec/models/todo_spec.rb | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index ab804d712a..d85f7bfdf5 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -61,14 +61,10 @@ class Todo < ActiveRecord::Base # override to return commits, which are not active record def target if for_commit? - project.commit(commit_id) + project.commit(commit_id) rescue nil else super end - # Temp fix to prevent app crash - # if note commit id doesn't exist - rescue - nil end def target_reference diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 22acfdfe2c..d9b86b9368 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -99,13 +99,23 @@ describe Todo, models: true do end describe '#target' do - it 'returns an instance of Commit for commits' do - subject.project = project - subject.target_type = 'Commit' - subject.commit_id = commit.id + context 'for commits' do + it 'returns an instance of Commit when exists' do + subject.project = project + subject.target_type = 'Commit' + subject.commit_id = commit.id - expect(subject.target).to be_a(Commit) - expect(subject.target).to eq commit + expect(subject.target).to be_a(Commit) + expect(subject.target).to eq commit + end + + it 'returns nil when does not exists' do + subject.project = project + subject.target_type = 'Commit' + subject.commit_id = 'xxxx' + + expect(subject.target).to be_nil + end end it 'returns the issuable for issuables' do From 3837f4413a0773d7d57a659d2ab8ccfcbe67ea7e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 18 Mar 2016 17:37:10 +0100 Subject: [PATCH 134/153] Re-group scss variables Signed-off-by: Dmitriy Zaporozhets --- .../stylesheets/framework/variables.scss | 68 +++++++++++++------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 211ead7319..61b52136c6 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -1,45 +1,71 @@ -$row-hover: #f4f8fe; +/* + * Layout + */ +$sidebar_collapsed_width: 62px; +$sidebar_width: 230px; +$gutter_collapsed_width: 62px; +$gutter_width: 290px; +$gutter_inner_width: 258px; + +/* + * UI elements + */ +$border-color: #efeff1; +$table-border-color: #eef0f2; +$background-color: #faf9f9; + +/* + * Text + */ +$secondary-text: #555; +$placeholder-color: #8f8f8f; $gl-text-color: #54565b; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; $gl-header-color: #323232; $gl-link-color: #333c48; +$gl-gray: #5a5a5a; $md-text-color: #444; $md-link-color: #3084bb; -$progress-color: #c0392b; -$gl-font-size: 15px; + +/* + * Lists + */ +$list-title-color: #333; +$list-text-color: #555; $list-font-size: 15px; -$sidebar_collapsed_width: 62px; -$sidebar_width: 230px; -$gutter_collapsed_width: 62px; -$gutter_width: 290px; -$gutter_inner_width: 258px; -$avatar_radius: 50%; + + +/* + * Code + */ $code_font_size: 13px; $code_line_height: 1.5; -$border-color: #efeff1; -$table-border-color: #eef0f2; -$background-color: #faf9f9; -$header-height: 58px; -$fixed-layout-width: 1280px; -$gl-gray: #5a5a5a; + +/* + * Padding + */ $gl-padding: 16px; $gl-btn-padding: 10px; $gl-vert-padding: 6px; $gl-padding-top: 10px; + +/* + * Misc + */ +$row-hover: #f4f8fe; +$progress-color: #c0392b; +$gl-font-size: 15px; +$avatar_radius: 50%; +$header-height: 58px; +$fixed-layout-width: 1280px; $gl-avatar-size: 40px; -$secondary-text: #7f8fa4; $error-exclamation-point: #e62958; $border-radius-default: 3px; -$list-title-color: #333; -$list-text-color: #555; - $btn-transparent-color: #8f8f8f; - $ssh-key-icon-color: #8f8f8f; $ssh-key-icon-size: 18px; - $provider-btn-group-border: #e5e5e5; $provider-btn-not-active-color: #4688f1; From 5e2728891ecc375ee51d17aad1c40898cf3e16c1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 18 Mar 2016 17:48:52 +0100 Subject: [PATCH 135/153] Cleanup somce css colors Signed-off-by: Dmitriy Zaporozhets --- .../stylesheets/framework/variables.scss | 25 +++++++++++-------- app/assets/stylesheets/pages/events.scss | 7 +++--- app/assets/stylesheets/pages/issues.scss | 6 ++--- app/assets/stylesheets/pages/todos.scss | 7 +++--- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 61b52136c6..d455a3d8e2 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -17,25 +17,29 @@ $background-color: #faf9f9; /* * Text */ -$secondary-text: #555; -$placeholder-color: #8f8f8f; -$gl-text-color: #54565b; +$gl-font-size: 15px; +$gl-title-color: #333; +$gl-text-color: #555; +$gl-placeholder-color: #8f8f8f; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; -$gl-header-color: #323232; +$gl-header-color: $gl-title-color; $gl-link-color: #333c48; -$gl-gray: #5a5a5a; -$md-text-color: #444; -$md-link-color: #3084bb; +$gl-gray: $gl-text-color; /* * Lists */ -$list-title-color: #333; -$list-text-color: #555; -$list-font-size: 15px; +$list-font-size: $gl-font-size; +$list-title-color: $gl-title-color; +$list-text-color: $gl-text-color; +/* + * Markdown + */ +$md-text-color: #444; +$md-link-color: #3084bb; /* * Code @@ -56,7 +60,6 @@ $gl-padding-top: 10px; */ $row-hover: #f4f8fe; $progress-color: #c0392b; -$gl-font-size: 15px; $avatar_radius: 50%; $header-height: 58px; $fixed-layout-width: 1280px; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index b39a9abf40..040e1d3867 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -6,7 +6,7 @@ font-size: $gl-font-size; padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top); border-bottom: 1px solid $table-border-color; - color: #7f8fa4; + color: $list-text-color; &.event-inline { .avatar { @@ -31,9 +31,10 @@ .event-title { @include str-truncated(calc(100% - 174px)); font-weight: 600; + color: $list-title-color; - .author_name { - color: #333; + a { + color: $list-title-color; } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 7ac4bc468d..76bd6d9a8a 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -130,14 +130,14 @@ form.edit-issue { } .issue-closed-by-widget { - color: $secondary-text; + color: $gl-text-color; margin-left: 52px; } .editor-details { display: block; - + @media (min-width: $screen-sm-min) { display: inline-block; } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 27970eba15..dbd6585fc3 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -16,7 +16,7 @@ .todo-item { font-size: $gl-font-size; padding-left: $gl-avatar-size + $gl-padding-top; - color: $secondary-text; + color: $gl-text-color; a { color: #4c4e54; @@ -29,9 +29,10 @@ .todo-title { @include str-truncated(calc(100% - 174px)); font-weight: 600; + color: $list-title-color; - .author-name { - color: #333; + a { + color: $list-title-color; } } From 5e39846e4b139428940c6a4677667c8b0fbb78ce Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Fri, 18 Mar 2016 16:21:09 +0100 Subject: [PATCH 136/153] Add avatar to issue and MR pages header --- app/views/projects/issues/show.html.haml | 4 ++-- app/views/projects/merge_requests/show/_mr_title.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index ce5b84ee71..52df3de8a2 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -32,9 +32,9 @@ = time_ago_with_tooltip(@issue.created_at) by %strong - = link_to_member(@project, @issue.author, avatar: false, size: 24, mobile_classes: "hidden-xs") + = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs") %strong - = link_to_member(@project, @issue.author, avatar: false, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", + = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", by_username: true, avatar: false) .pull-right.issue-btn-group diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index c6cbe8589e..eeb605e2dc 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -19,9 +19,9 @@ = time_ago_with_tooltip(@merge_request.created_at) by %strong - = link_to_member(@project, @merge_request.author, avatar: false, size: 24, mobile_classes: "hidden-xs") + = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs") %strong - = link_to_member(@project, @merge_request.author, avatar: false, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", + = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", by_username: true, avatar: false) .issue-btn-group.pull-right From c9e202c1d7cab4918e8b3789e9e6a553c30ead2a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 8 Mar 2016 02:56:43 -0500 Subject: [PATCH 137/153] Working version of autocomplete with categorized results --- app/assets/javascripts/dispatcher.js.coffee | 7 +- .../lib/category_autocomplete.js.coffee | 17 ++ .../javascripts/search_autocomplete.js.coffee | 169 +++++++++++++++++- app/helpers/search_helper.rb | 59 +++--- app/views/layouts/_search.html.haml | 13 +- app/views/shared/_location_badge.html.haml | 13 ++ 6 files changed, 229 insertions(+), 49 deletions(-) create mode 100644 app/assets/javascripts/lib/category_autocomplete.js.coffee create mode 100644 app/views/shared/_location_badge.html.haml diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index f5e1ca9860..a022c207d0 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -152,9 +152,4 @@ class Dispatcher new Shortcuts() initSearch: -> - opts = $('.search-autocomplete-opts') - path = opts.data('autocomplete-path') - project_id = opts.data('autocomplete-project-id') - project_ref = opts.data('autocomplete-project-ref') - - new SearchAutocomplete(path, project_id, project_ref) + new SearchAutocomplete() diff --git a/app/assets/javascripts/lib/category_autocomplete.js.coffee b/app/assets/javascripts/lib/category_autocomplete.js.coffee new file mode 100644 index 0000000000..490032dc78 --- /dev/null +++ b/app/assets/javascripts/lib/category_autocomplete.js.coffee @@ -0,0 +1,17 @@ +$.widget( "custom.catcomplete", $.ui.autocomplete, + _create: -> + @_super(); + @widget().menu("option", "items", "> :not(.ui-autocomplete-category)") + + _renderMenu: (ul, items) -> + currentCategory = '' + $.each items, (index, item) => + if item.category isnt currentCategory + ul.append("
  • #{item.category}
  • ") + currentCategory = item.category + + li = @_renderItemData(ul, item) + + if item.category? + li.attr('aria-label', item.category + " : " + item.label) + ) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index c180136526..df31b07910 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -1,11 +1,164 @@ class @SearchAutocomplete - constructor: (search_autocomplete_path, project_id, project_ref) -> - project_id = '' unless project_id - project_ref = '' unless project_ref - query = "?project_id=" + project_id + "&project_ref=" + project_ref + constructor: (opts = {}) -> + { + @wrap = $('.search') + @optsEl = @wrap.find('.search-autocomplete-opts') + @autocompletePath = @optsEl.data('autocomplete-path') + @projectId = @optsEl.data('autocomplete-project-id') || '' + @projectRef = @optsEl.data('autocomplete-project-ref') || '' + } = opts - $("#search").autocomplete - source: search_autocomplete_path + query + @keyCode = + ESCAPE: 27 + BACKSPACE: 8 + TAB: 9 + ENTER: 13 + + @locationBadgeEl = @$('.search-location-badge') + @locationText = @$('.location-text') + @searchInput = @$('.search-input') + @projectInputEl = @$('#project_id') + @groupInputEl = @$('#group_id') + @searchCodeInputEl = @$('#search_code') + @repositoryInputEl = @$('#repository_ref') + @scopeInputEl = @$('#scope') + + @saveOriginalState() + @createAutocomplete() + @bindEvents() + + $: (selector) -> + @wrap.find(selector) + + saveOriginalState: -> + @originalState = @serializeState() + + restoreOriginalState: -> + inputs = Object.keys @originalState + + for input in inputs + @$("##{input}").val(@originalState[input]) + + + if @originalState._location is '' + @locationBadgeEl.html('') + else + @addLocationBadge( + value: @originalState._location + ) + + serializeState: -> + { + # Search Criteria + project_id: @projectInputEl.val() + group_id: @groupInputEl.val() + search_code: @searchCodeInputEl.val() + repository_ref: @repositoryInputEl.val() + + # Location badge + _location: $.trim(@locationText.text()) + } + + createAutocomplete: -> + @query = "?project_id=" + @projectId + "&project_ref=" + @projectRef + + @catComplete = @searchInput.catcomplete + appendTo: 'form.navbar-form' + source: @autocompletePath + @query minLength: 1 - select: (event, ui) -> - location.href = ui.item.url + close: (e) -> + e.preventDefault() + + select: (event, ui) => + # Pressing enter choses an alternative + if event.keyCode is @keyCode.ENTER + @goToResult(ui.item) + else + # Pressing tab sets the scope + if event.keyCode is @keyCode.TAB and ui.item.scope? + @setLocationBadge(ui.item) + @searchInput + .val('') # remove selected value from input + .focus() + else + # If option is not a scope go to page + @goToResult(ui.item) + + # Return false to avoid focus on the next element + return false + + + bindEvents: -> + @searchInput.on 'keydown', @onSearchKeyDown + @wrap.on 'click', '.remove-badge', @onRemoveLocationBadgeClick + + onRemoveLocationBadgeClick: (e) => + e.preventDefault() + @removeLocationBadge() + @searchInput.focus() + + onSearchKeyDown: (e) => + # Remove tag when pressing backspace and input search is empty + if e.keyCode is @keyCode.BACKSPACE and e.currentTarget.value is '' + @removeLocationBadge() + @destroyAutocomplete() + @searchInput.focus() + else if e.keyCode is @keyCode.ESCAPE + @restoreOriginalState() + else + # Create new autocomplete instance if it's not created + @createAutocomplete() unless @catcomplete? + + addLocationBadge: (item) -> + category = if item.category? then "#{item.category}: " else '' + value = if item.value? then item.value else '' + + html = " + #{category}#{value} + x + " + @locationBadgeEl.html(html) + + setLocationBadge: (item) -> + @addLocationBadge(item) + + # Reset input states + @resetSearchState() + + switch item.scope + when 'projects' + @projectInputEl.val(item.id) + # @searchCodeInputEl.val('true') # TODO: always true for projects? + # @repositoryInputEl.val('master') # TODO: always master? + + when 'groups' + @groupInputEl.val(item.id) + + removeLocationBadge: -> + @locationBadgeEl.empty() + + # Reset state + @resetSearchState() + + resetSearchState: -> + # Remove scope + @scopeInputEl.val('') + + # Remove group + @groupInputEl.val('') + + # Remove project id + @projectInputEl.val('') + + # Remove code search + @searchCodeInputEl.val('') + + # Remove repository ref + @repositoryInputEl.val('') + + goToResult: (result) -> + location.href = result.url + + destroyAutocomplete: -> + @catComplete.destroy() if @catcomplete? + @catComplete = null diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 494dad0b41..9102fd6d50 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -23,45 +23,45 @@ module SearchHelper # Autocomplete results for various settings pages def default_autocomplete [ - { label: "Profile settings", url: profile_path }, - { label: "SSH Keys", url: profile_keys_path }, - { label: "Dashboard", url: root_path }, - { label: "Admin Section", url: admin_root_path }, + { category: "Settings", label: "Profile settings", url: profile_path }, + { category: "Settings", label: "SSH Keys", url: profile_keys_path }, + { category: "Settings", label: "Dashboard", url: root_path }, + { category: "Settings", label: "Admin Section", url: admin_root_path }, ] end # Autocomplete results for internal help pages def help_autocomplete [ - { label: "help: API Help", url: help_page_path("api", "README") }, - { label: "help: Markdown Help", url: help_page_path("markdown", "markdown") }, - { label: "help: Permissions Help", url: help_page_path("permissions", "permissions") }, - { label: "help: Public Access Help", url: help_page_path("public_access", "public_access") }, - { label: "help: Rake Tasks Help", url: help_page_path("raketasks", "README") }, - { label: "help: SSH Keys Help", url: help_page_path("ssh", "README") }, - { label: "help: System Hooks Help", url: help_page_path("system_hooks", "system_hooks") }, - { label: "help: Webhooks Help", url: help_page_path("web_hooks", "web_hooks") }, - { label: "help: Workflow Help", url: help_page_path("workflow", "README") }, + { category: "Help", label: "API Help", url: help_page_path("api", "README") }, + { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") }, + { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") }, + { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") }, + { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") }, + { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") }, + { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") }, + { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") }, + { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") }, ] end # Autocomplete results for the current project, if it's defined def project_autocomplete if @project && @project.repository.exists? && @project.repository.root_ref - prefix = search_result_sanitize(@project.name_with_namespace) + prefix = "Project - " + search_result_sanitize(@project.name_with_namespace) ref = @ref || @project.repository.root_ref [ - { label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, - { label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, - { label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, - { label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, - { label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) }, - { label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, - { label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, - { label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, - { label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) }, - { label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, + { category: prefix, label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, + { category: prefix, label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, + { category: prefix, label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, + { category: prefix, label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, + { category: prefix, label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) }, + { category: prefix, label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, + { category: prefix, label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, + { category: prefix, label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, + { category: prefix, label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) }, + { category: prefix, label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, ] else [] @@ -72,7 +72,10 @@ module SearchHelper def groups_autocomplete(term, limit = 5) current_user.authorized_groups.search(term).limit(limit).map do |group| { - label: "group: #{search_result_sanitize(group.name)}", + category: "Groups", + scope: "groups", + id: group.id, + label: "#{search_result_sanitize(group.name)}", url: group_path(group) } end @@ -83,7 +86,11 @@ module SearchHelper current_user.authorized_projects.search_by_title(term). sorted_by_stars.non_archived.limit(limit).map do |p| { - label: "project: #{search_result_sanitize(p.name_with_namespace)}", + category: "Projects", + scope: "projects", + id: p.id, + value: "#{search_result_sanitize(p.name)}", + label: "#{search_result_sanitize(p.name_with_namespace)}", url: namespace_project_path(p.namespace, p) } end diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 54af2c3063..c500289383 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,10 +1,12 @@ .search = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| + = render 'shared/location_badge' = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1" = hidden_field_tag :group_id, @group.try(:id) - - if @project && @project.persisted? - = hidden_field_tag :project_id, @project.id + = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '' + + - if @project && @project.persisted? - if current_controller?(:issues) = hidden_field_tag :scope, 'issues' - elsif current_controller?(:merge_requests) @@ -21,10 +23,3 @@ = hidden_field_tag :repository_ref, @ref = button_tag 'Go' if ENV['RAILS_ENV'] == 'test' .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } - -:javascript - $('.search-input').on('keyup', function(e) { - if (e.keyCode == 27) { - $('.search-input').blur(); - } - }); diff --git a/app/views/shared/_location_badge.html.haml b/app/views/shared/_location_badge.html.haml new file mode 100644 index 0000000000..dfe8bc010d --- /dev/null +++ b/app/views/shared/_location_badge.html.haml @@ -0,0 +1,13 @@ +- if controller.controller_path =~ /^groups/ + - label = 'This group' +- if controller.controller_path =~ /^projects/ + - label = 'This project' + +.search-location-badge + - if label.present? + %span.label.label-primary + %i.location-text + = label + + %a.remove-badge{href: '#'} + x From 6a7f4a0767f059a2a3e4e4a4999046cffa860561 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 8 Mar 2016 19:39:14 -0500 Subject: [PATCH 138/153] Apply styling and tweaks to autocomplete dropdown --- .../lib/category_autocomplete.js.coffee | 32 +++++++++ .../javascripts/search_autocomplete.js.coffee | 35 ++++++++-- app/assets/stylesheets/framework/forms.scss | 34 --------- app/assets/stylesheets/framework/header.scss | 20 ------ app/assets/stylesheets/framework/jquery.scss | 34 ++++++++- app/assets/stylesheets/pages/search.scss | 70 +++++++++++++++++++ app/helpers/search_helper.rb | 21 +++--- app/views/layouts/_search.html.haml | 13 ++-- app/views/shared/_location_badge.html.haml | 13 ++-- 9 files changed, 186 insertions(+), 86 deletions(-) diff --git a/app/assets/javascripts/lib/category_autocomplete.js.coffee b/app/assets/javascripts/lib/category_autocomplete.js.coffee index 490032dc78..c85fabbcd5 100644 --- a/app/assets/javascripts/lib/category_autocomplete.js.coffee +++ b/app/assets/javascripts/lib/category_autocomplete.js.coffee @@ -14,4 +14,36 @@ $.widget( "custom.catcomplete", $.ui.autocomplete, if item.category? li.attr('aria-label', item.category + " : " + item.label) + + _renderItem: (ul, item) -> + # Highlight occurrences + item.label = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(this.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + + return $( "
  • " ) + .data( "item.autocomplete", item ) + .append( "#{item.label}" ) + .appendTo( ul ); + + _resizeMenu: -> + if (isNaN(this.options.maxShowItems)) + return + + ul = this.menu.element.css(overflowX: '', overflowY: '', width: '', maxHeight: '') + + lis = ul.children('li').css('whiteSpace', 'nowrap'); + + if (lis.length > this.options.maxShowItems) + ulW = ul.prop('clientWidth') + + ul.css( + overflowX: 'hidden' + overflowY: 'auto' + maxHeight: lis.eq(0).outerHeight() * this.options.maxShowItems + 1 + ) + + barW = ulW - ul.prop('clientWidth'); + ul.width('+=' + barW); + + # Original code from jquery.ui.autocomplete.js _resizeMenu() + ul.outerWidth(Math.max(ul.outerWidth() + 1, this.element.outerWidth())); ) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index df31b07910..a6d5ab6523 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -24,7 +24,10 @@ class @SearchAutocomplete @scopeInputEl = @$('#scope') @saveOriginalState() - @createAutocomplete() + + if @locationBadgeEl.is(':empty') + @createAutocomplete() + @bindEvents() $: (selector) -> @@ -66,6 +69,12 @@ class @SearchAutocomplete appendTo: 'form.navbar-form' source: @autocompletePath + @query minLength: 1 + maxShowItems: 15 + position: + # { my: "left top", at: "left bottom", collision: "none" } + my: "left-10 top+9" + at: "left bottom" + collision: "none" close: (e) -> e.preventDefault() @@ -89,7 +98,9 @@ class @SearchAutocomplete bindEvents: -> - @searchInput.on 'keydown', @onSearchKeyDown + @searchInput.on 'keydown', @onSearchInputKeyDown + @searchInput.on 'focus', @onSearchInputFocus + @searchInput.on 'blur', @onSearchInputBlur @wrap.on 'click', '.remove-badge', @onRemoveLocationBadgeClick onRemoveLocationBadgeClick: (e) => @@ -97,7 +108,7 @@ class @SearchAutocomplete @removeLocationBadge() @searchInput.focus() - onSearchKeyDown: (e) => + onSearchInputKeyDown: (e) => # Remove tag when pressing backspace and input search is empty if e.keyCode is @keyCode.BACKSPACE and e.currentTarget.value is '' @removeLocationBadge() @@ -106,14 +117,24 @@ class @SearchAutocomplete else if e.keyCode is @keyCode.ESCAPE @restoreOriginalState() else - # Create new autocomplete instance if it's not created - @createAutocomplete() unless @catcomplete? + # Create new autocomplete if hasn't been created yet and there's no badge + if !@catComplete? and @locationBadgeEl.is(':empty') + @createAutocomplete() + + onSearchInputFocus: => + @wrap.addClass('search-active') + + onSearchInputBlur: => + @wrap.removeClass('search-active') + + # If input is blank then restore state + @restoreOriginalState() if @searchInput.val() is '' addLocationBadge: (item) -> category = if item.category? then "#{item.category}: " else '' value = if item.value? then item.value else '' - html = " + html = " #{category}#{value} x " @@ -160,5 +181,5 @@ class @SearchAutocomplete location.href = result.url destroyAutocomplete: -> - @catComplete.destroy() if @catcomplete? + @catComplete.destroy() if @catComplete? @catComplete = null diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 4cb4129b71..91b6451e68 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -6,40 +6,6 @@ input { border-radius: $border-radius-base; } -input[type='search'] { - background-color: white; - padding-left: 10px; -} - -input[type='search'].search-input { - background-repeat: no-repeat; - background-position: 10px; - background-size: 16px; - background-position-x: 30%; - padding-left: 10px; - background-color: $gray-light; - - &.search-input[value=""] { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC'); - } - - &.search-input::-webkit-input-placeholder { - text-align: center; - } - - &.search-input:-moz-placeholder { /* Firefox 18- */ - text-align: center; - } - - &.search-input::-moz-placeholder { /* Firefox 19+ */ - text-align: center; - } - - &.search-input:-ms-input-placeholder { - text-align: center; - } -} - input[type='text'].danger { background: #f2dede!important; border-color: #d66; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 71a7ecab8e..a6c9fce5b8 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -112,26 +112,6 @@ header { } } - .search { - margin-right: 10px; - margin-left: 10px; - margin-top: ($header-height - 36) / 2; - - form { - margin: 0; - padding: 0; - } - - .search-input { - width: 220px; - - &:focus { - @include box-shadow(none); - outline: none; - } - } - } - .impersonation i { color: $red-normal; } diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 525ed81b05..e0d655d305 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -23,9 +23,39 @@ padding: 0; margin-top: 2px; z-index: 1001; + width: 240px; + margin-bottom: 0; + padding: 10px 10px; + font-size: 14px; + font-weight: normal; + background-color: $dropdown-bg; + border: 1px solid $dropdown-border-color; + border-radius: $border-radius-base; + box-shadow: 0 2px 4px $dropdown-shadow-color; - .ui-menu-item a { - padding: 4px 10px; + .ui-menu-item { + display: block; + position: relative; + padding: 0 10px; + color: $dropdown-link-color; + line-height: 34px; + text-overflow: ellipsis; + border-radius: 2px; + white-space: nowrap; + overflow: hidden; + border: none; + + &.ui-state-focus { + background-color: $dropdown-link-hover-bg; + text-decoration: none; + margin: 0; + } + } + + .ui-autocomplete-category { + text-transform: uppercase; + font-size: 11px; + color: #7f8fa4; } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index b6e4502464..57b88268c0 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -21,3 +21,73 @@ } } + +.search { + margin-right: 10px; + margin-left: 10px; + margin-top: ($header-height - 35) / 2; + + &.search-active { + form { + @extend .form-control:focus; + } + + .location-badge { + @include transition(all .15s); + background-color: $input-border-focus; + color: $white-light; + } + } + + form { + @extend .form-control; + margin: 0; + padding: 4px; + width: 350px; + line-height: 24px; + overflow: hidden; + } + + .location-text { + font-style: normal; + } + + .remove-badge { + display: none; + } + + .search-input { + border: none; + font-size: 14px; + outline: none; + padding: 0; + margin-left: 2px; + line-height: 25px; + width: 100%; + } + + .location-badge { + line-height: 25px; + padding: 0 5px; + border-radius: 2px; + font-size: 14px; + font-style: normal; + color: #AAAAAA; + display: inline-block; + background-color: #F5F5F5; + vertical-align: top; + } + + .search-input-container { + display: flex; + } + + .search-location-badge, .search-input-wrap { + // Fallback if flex is not supported + display: inline-block; + } + + .search-input-wrap { + width: 100%; + } +} diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 9102fd6d50..cbead1b8b7 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -48,20 +48,19 @@ module SearchHelper # Autocomplete results for the current project, if it's defined def project_autocomplete if @project && @project.repository.exists? && @project.repository.root_ref - prefix = "Project - " + search_result_sanitize(@project.name_with_namespace) ref = @ref || @project.repository.root_ref [ - { category: prefix, label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) }, - { category: prefix, label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, - { category: prefix, label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, - { category: prefix, label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, - { category: prefix, label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) }, - { category: prefix, label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, + { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) }, + { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, + { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, + { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, + { category: "Current Project", label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) }, + { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, ] else [] diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index c500289383..843c833b4f 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,9 +1,12 @@ -.search - = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| - = render 'shared/location_badge' - = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1" - = hidden_field_tag :group_id, @group.try(:id) +.search.search-form + = form_tag search_path, method: :get, class: 'navbar-form' do |f| + .search-input-container + .search-location-badge + = render 'shared/location_badge' + .search-input-wrap + = search_field_tag "search", nil, placeholder: 'Search', class: "search-input", spellcheck: false, tabindex: "1", autocomplete: 'off' + = hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '' - if @project && @project.persisted? diff --git a/app/views/shared/_location_badge.html.haml b/app/views/shared/_location_badge.html.haml index dfe8bc010d..f1ecc060cf 100644 --- a/app/views/shared/_location_badge.html.haml +++ b/app/views/shared/_location_badge.html.haml @@ -3,11 +3,10 @@ - if controller.controller_path =~ /^projects/ - label = 'This project' -.search-location-badge - - if label.present? - %span.label.label-primary - %i.location-text - = label +- if label.present? + %span.location-badge + %i.location-text + = label - %a.remove-badge{href: '#'} - x + %a.remove-badge{href: '#'} + x From 54797957087a41fdf84f33ca6a83be38729a9f64 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 8 Mar 2016 21:26:24 -0500 Subject: [PATCH 139/153] Tweak behaviours --- .../javascripts/search_autocomplete.js.coffee | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index a6d5ab6523..3cedf1c7b1 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -25,7 +25,8 @@ class @SearchAutocomplete @saveOriginalState() - if @locationBadgeEl.is(':empty') + # If there's no location badge + if !@locationBadgeEl.children().length @createAutocomplete() @bindEvents() @@ -65,7 +66,7 @@ class @SearchAutocomplete createAutocomplete: -> @query = "?project_id=" + @projectId + "&project_ref=" + @projectRef - @catComplete = @searchInput.catcomplete + @searchInput.catcomplete appendTo: 'form.navbar-form' source: @autocompletePath + @query minLength: 1 @@ -96,6 +97,7 @@ class @SearchAutocomplete # Return false to avoid focus on the next element return false + @autocomplete = @searchInput.data 'customCatcomplete' bindEvents: -> @searchInput.on 'keydown', @onSearchInputKeyDown @@ -112,14 +114,19 @@ class @SearchAutocomplete # Remove tag when pressing backspace and input search is empty if e.keyCode is @keyCode.BACKSPACE and e.currentTarget.value is '' @removeLocationBadge() - @destroyAutocomplete() + # @destroyAutocomplete() @searchInput.focus() else if e.keyCode is @keyCode.ESCAPE @restoreOriginalState() else # Create new autocomplete if hasn't been created yet and there's no badge - if !@catComplete? and @locationBadgeEl.is(':empty') - @createAutocomplete() + if @autocomplete is undefined + if !@locationBadgeEl.children().length + @createAutocomplete() + else + # There's a badge + if @locationBadgeEl.children().length + @destroyAutocomplete() onSearchInputFocus: => @wrap.addClass('search-active') @@ -181,5 +188,6 @@ class @SearchAutocomplete location.href = result.url destroyAutocomplete: -> - @catComplete.destroy() if @catComplete? - @catComplete = null + @autocomplete.destroy() if @autocomplete isnt undefined + @searchInput.attr('autocomplete', 'off') + @autocomplete = undefined From 82e3c1f257e5b2ecaf4e52a01e3d6379e98684a7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 9 Mar 2016 12:45:43 -0500 Subject: [PATCH 140/153] Change hidden input id to avoid duplicated IDs The TODOs dashboard already had a #project_id input and it was causing a spec to fail --- app/assets/javascripts/search_autocomplete.js.coffee | 2 +- app/views/layouts/_search.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 3cedf1c7b1..0c4876358b 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -17,7 +17,7 @@ class @SearchAutocomplete @locationBadgeEl = @$('.search-location-badge') @locationText = @$('.location-text') @searchInput = @$('.search-input') - @projectInputEl = @$('#project_id') + @projectInputEl = @$('#search_project_id') @groupInputEl = @$('#group_id') @searchCodeInputEl = @$('#search_code') @repositoryInputEl = @$('#repository_ref') diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 843c833b4f..58a3cdf955 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -7,7 +7,7 @@ = search_field_tag "search", nil, placeholder: 'Search', class: "search-input", spellcheck: false, tabindex: "1", autocomplete: 'off' = hidden_field_tag :group_id, @group.try(:id) - = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '' + = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id' - if @project && @project.persisted? - if current_controller?(:issues) From ec0dfff2048b79087204de9083f4f1eca8446650 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 9 Mar 2016 15:52:15 -0500 Subject: [PATCH 141/153] Add icons --- app/assets/images/spinner.svg | 1 + app/assets/stylesheets/pages/search.scss | 39 +++++++++++++++++++++++- app/views/layouts/_search.html.haml | 1 + 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 app/assets/images/spinner.svg diff --git a/app/assets/images/spinner.svg b/app/assets/images/spinner.svg new file mode 100644 index 0000000000..3dd110cfa0 --- /dev/null +++ b/app/assets/images/spinner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 57b88268c0..bcbdbb07ed 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -37,6 +37,12 @@ background-color: $input-border-focus; color: $white-light; } + + .search-input-wrap { + i { + color: $input-border-focus; + } + } } form { @@ -61,7 +67,7 @@ font-size: 14px; outline: none; padding: 0; - margin-left: 2px; + margin-left: 5px; line-height: 25px; width: 100%; } @@ -89,5 +95,36 @@ .search-input-wrap { width: 100%; + position: relative; + + .search-icon { + @extend .fa-search; + @include transition(color .15s); + position: absolute; + right: 5px; + color: #E7E9ED; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + + &:before { + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + } + } + + .ui-autocomplete-loading + .search-icon { + height: 25px; + width: 25px; + position: absolute; + right: 0; + background-image: image-url('spinner.svg'); + fill: red; + + &:before { + display: none; + } + } } } diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 58a3cdf955..a004908fb6 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -5,6 +5,7 @@ = render 'shared/location_badge' .search-input-wrap = search_field_tag "search", nil, placeholder: 'Search', class: "search-input", spellcheck: false, tabindex: "1", autocomplete: 'off' + %i.search-icon = hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id' From 2541f59227013055733f5cdf1f36835436f196dc Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 9 Mar 2016 16:34:21 -0500 Subject: [PATCH 142/153] Better wording --- app/assets/javascripts/search_autocomplete.js.coffee | 8 ++++---- app/helpers/search_helper.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 0c4876358b..b867190086 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -84,14 +84,14 @@ class @SearchAutocomplete if event.keyCode is @keyCode.ENTER @goToResult(ui.item) else - # Pressing tab sets the scope - if event.keyCode is @keyCode.TAB and ui.item.scope? + # Pressing tab sets the location + if event.keyCode is @keyCode.TAB and ui.item.location? @setLocationBadge(ui.item) @searchInput .val('') # remove selected value from input .focus() else - # If option is not a scope go to page + # If option is not a location go to page @goToResult(ui.item) # Return false to avoid focus on the next element @@ -153,7 +153,7 @@ class @SearchAutocomplete # Reset input states @resetSearchState() - switch item.scope + switch item.location when 'projects' @projectInputEl.val(item.id) # @searchCodeInputEl.val('true') # TODO: always true for projects? diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index cbead1b8b7..de16454739 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -72,7 +72,7 @@ module SearchHelper current_user.authorized_groups.search(term).limit(limit).map do |group| { category: "Groups", - scope: "groups", + location: "groups", id: group.id, label: "#{search_result_sanitize(group.name)}", url: group_path(group) @@ -86,7 +86,7 @@ module SearchHelper sorted_by_stars.non_archived.limit(limit).map do |p| { category: "Projects", - scope: "projects", + location: "projects", id: p.id, value: "#{search_result_sanitize(p.name)}", label: "#{search_result_sanitize(p.name_with_namespace)}", From dccda7d09f69ffef50de8ea6194ad105194a41ed Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 9 Mar 2016 22:16:30 -0500 Subject: [PATCH 143/153] Replace spinner icon for th FontAwesome one --- app/assets/images/spinner.svg | 1 - app/assets/stylesheets/pages/search.scss | 12 ++---------- 2 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 app/assets/images/spinner.svg diff --git a/app/assets/images/spinner.svg b/app/assets/images/spinner.svg deleted file mode 100644 index 3dd110cfa0..0000000000 --- a/app/assets/images/spinner.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index bcbdbb07ed..fa45d750f2 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -115,16 +115,8 @@ } .ui-autocomplete-loading + .search-icon { - height: 25px; - width: 25px; - position: absolute; - right: 0; - background-image: image-url('spinner.svg'); - fill: red; - - &:before { - display: none; - } + @extend .fa-spinner; + @extend .fa-spin; } } } From a0c1aa6d046b8dc20b75f3a6fe5f82e5055666a2 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 11 Mar 2016 13:18:38 -0500 Subject: [PATCH 144/153] Allow to pass non-asynchronous data to GitLabDropdown --- app/assets/javascripts/gl_dropdown.js.coffee | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index c81e8bf760..36b41072eb 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -83,15 +83,19 @@ class GitLabDropdown search_fields = if @options.search then @options.search.fields else []; if @options.data - # Remote data - @remote = new GitLabDropdownRemote @options.data, { - dataType: @options.dataType, - beforeSend: @toggleLoading.bind(@) - success: (data) => - @fullData = data + # If data is an array + if _.isArray @options.data + @parseData @options.data + else + # Remote data + @remote = new GitLabDropdownRemote @options.data, { + dataType: @options.dataType, + beforeSend: @toggleLoading.bind(@) + success: (data) => + @fullData = data - @parseData @fullData - } + @parseData @fullData + } # Init filiterable if @options.filterable @@ -204,7 +208,12 @@ class GitLabDropdown else selected = if @options.isSelected then @options.isSelected(data) else false url = if @options.url then @options.url(data) else "#" - text = if @options.text then @options.text(data) else "" + + if @options.text? + text = @options.text(data) + else + text = data.text if data.text? + cssClass = ""; if selected From 0b893bec544e2b1816979343f5f1c669482bfca4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 11 Mar 2016 13:39:28 -0500 Subject: [PATCH 145/153] Allow data with desired format --- app/assets/javascripts/gl_dropdown.js.coffee | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 36b41072eb..cdde52a38a 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -209,10 +209,17 @@ class GitLabDropdown selected = if @options.isSelected then @options.isSelected(data) else false url = if @options.url then @options.url(data) else "#" + # Set URL + if @options.url? + url = @options.url(data) + else + url = if data.url? then data.url else '' + + # Set Text if @options.text? text = @options.text(data) else - text = data.text if data.text? + text = if data.text? then data.text else '' cssClass = ""; From 4e486c6116002e35ce79284e9ce26f8612674021 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 11 Mar 2016 14:59:50 -0500 Subject: [PATCH 146/153] Allow to pass input filter param This allow us to set a different input to filter results --- app/assets/javascripts/gl_dropdown.js.coffee | 28 +++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index cdde52a38a..4e733165b4 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -2,7 +2,9 @@ class GitLabDropdownFilter BLUR_KEYCODES = [27, 40] constructor: (@dropdown, @options) -> - @input = @dropdown.find(".dropdown-input .dropdown-input-field") + { + @input + } = @options # Key events timeout = "" @@ -77,14 +79,30 @@ class GitLabDropdown PAGE_TWO_CLASS = "is-page-two" ACTIVE_CLASS = "is-active" + FILTER_INPUT = '.dropdown-input .dropdown-input-field' + constructor: (@el, @options) -> - self = @ @dropdown = $(@el).parent() + + # Set Defaults + { + # If no input is passed create a default one + @filterInput = @$(FILTER_INPUT) + } = @options + + self = @ + + # If selector was passed + if _.isString(@filterInput) + @filterInput = @$(@filterInput) + + search_fields = if @options.search then @options.search.fields else []; if @options.data # If data is an array if _.isArray @options.data + @fullData = @options.data @parseData @options.data else # Remote data @@ -100,6 +118,7 @@ class GitLabDropdown # Init filiterable if @options.filterable @filter = new GitLabDropdownFilter @dropdown, + input: @filterInput remote: @options.filterRemote query: @options.data keys: @options.search.fields @@ -133,6 +152,9 @@ class GitLabDropdown if self.options.clicked self.options.clicked() + $: (selector) -> + $(selector, @dropdown) + toggleLoading: -> $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS @@ -167,7 +189,7 @@ class GitLabDropdown @remote.execute() if @options.filterable - @dropdown.find(".dropdown-input-field").focus() + @filterInput.focus() hidden: => if @options.filterable From f858ee43eacc4cfdc24e38edafe39a6d7bc5e415 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 11 Mar 2016 18:00:17 -0500 Subject: [PATCH 147/153] Allow to pass header items --- app/assets/javascripts/gl_dropdown.js.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 4e733165b4..78e19064e5 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -222,8 +222,12 @@ class GitLabDropdown renderItem: (data) -> html = "" + # Separator return "
  • " if data is "divider" + # Header + return "" if data.header? + if @options.renderRow # Call the render function html = @options.renderRow(data) From b5bd497a1f85727a76f1a604ffba30783a8457ac Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 11 Mar 2016 20:38:19 -0500 Subject: [PATCH 148/153] Allow to hightlight matches --- app/assets/javascripts/gl_dropdown.js.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 78e19064e5..e1fae7bbc5 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -252,6 +252,8 @@ class GitLabDropdown if selected cssClass = "is-active" + text = @highlightTextMatches(text, @filterInput.val()) + html = "
  • " html += "" html += text @@ -260,6 +262,15 @@ class GitLabDropdown return html + highlightTextMatches: (text, term) -> + occurrences = fuzzaldrinPlus.match(text, term) + textArr = text.split('') + textArr.forEach (character, i, textArr) -> + if i in occurrences + textArr[i] = "#{character}" + + textArr.join '' + noResults: -> html = "
  • " html += "" From 2d0f8f928ce4dd0ba2db58e2434b81f153007aec Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 11 Mar 2016 20:47:01 -0500 Subject: [PATCH 149/153] Disable highlighting by default --- app/assets/javascripts/gl_dropdown.js.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index e1fae7bbc5..53bbbfbaf5 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -88,6 +88,7 @@ class GitLabDropdown { # If no input is passed create a default one @filterInput = @$(FILTER_INPUT) + @highlight = false } = @options self = @ @@ -252,7 +253,8 @@ class GitLabDropdown if selected cssClass = "is-active" - text = @highlightTextMatches(text, @filterInput.val()) + if @highlight + text = @highlightTextMatches(text, @filterInput.val()) html = "
  • " html += "" From 53df7263b7e898ab118714f5da818476c0a75e6d Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 14 Mar 2016 16:14:29 -0500 Subject: [PATCH 150/153] Use new dropdown class for search suggestions --- app/assets/javascripts/gl_dropdown.js.coffee | 5 +- .../javascripts/search_autocomplete.js.coffee | 266 +++++++++--------- app/assets/stylesheets/framework/jquery.scss | 6 - app/assets/stylesheets/pages/search.scss | 13 +- app/views/layouts/_search.html.haml | 6 +- 5 files changed, 154 insertions(+), 142 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 53bbbfbaf5..3d2e3f3dbb 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -4,6 +4,7 @@ class GitLabDropdownFilter constructor: (@dropdown, @options) -> { @input + @filterInputBlur = true } = @options # Key events @@ -19,7 +20,7 @@ class GitLabDropdownFilter blur_field = @shouldBlur e.keyCode search_text = @input.val() - if blur_field + if blur_field && @filterInputBlur @input.blur() if @options.remote @@ -89,6 +90,7 @@ class GitLabDropdown # If no input is passed create a default one @filterInput = @$(FILTER_INPUT) @highlight = false + @filterInputBlur = true } = @options self = @ @@ -119,6 +121,7 @@ class GitLabDropdown # Init filiterable if @options.filterable @filter = new GitLabDropdownFilter @dropdown, + filterInputBlur: @filterInputBlur input: @filterInput remote: @options.filterRemote query: @options.data diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index b867190086..e21a140b2a 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -1,21 +1,28 @@ class @SearchAutocomplete + + KEYCODE = + ESCAPE: 27 + BACKSPACE: 8 + TAB: 9 + ENTER: 13 + constructor: (opts = {}) -> { @wrap = $('.search') + @optsEl = @wrap.find('.search-autocomplete-opts') @autocompletePath = @optsEl.data('autocomplete-path') @projectId = @optsEl.data('autocomplete-project-id') || '' @projectRef = @optsEl.data('autocomplete-project-ref') || '' + } = opts - @keyCode = - ESCAPE: 27 - BACKSPACE: 8 - TAB: 9 - ENTER: 13 + # Dropdown Element + @dropdown = @wrap.find('.dropdown') @locationBadgeEl = @$('.search-location-badge') @locationText = @$('.location-text') + @scopeInputEl = @$('#scope') @searchInput = @$('.search-input') @projectInputEl = @$('#search_project_id') @groupInputEl = @$('#group_id') @@ -25,9 +32,7 @@ class @SearchAutocomplete @saveOriginalState() - # If there's no location badge - if !@locationBadgeEl.children().length - @createAutocomplete() + @searchInput.addClass('disabled') @bindEvents() @@ -37,6 +42,118 @@ class @SearchAutocomplete saveOriginalState: -> @originalState = @serializeState() + serializeState: -> + { + # Search Criteria + project_id: @projectInputEl.val() + group_id: @groupInputEl.val() + search_code: @searchCodeInputEl.val() + repository_ref: @repositoryInputEl.val() + + # Location badge + _location: $.trim(@locationText.text()) + } + + bindEvents: -> + @searchInput.on 'keydown', @onSearchInputKeyDown + @searchInput.on 'focus', @onSearchInputFocus + @searchInput.on 'blur', @onSearchInputBlur + + enableAutocomplete: -> + self = @ + @query = "?project_id=" + @projectId + "&project_ref=" + @projectRef + dropdownMenu = self.dropdown.find('.dropdown-menu') + + @searchInput.glDropdown( + filterInputBlur: false + filterable: true + filterRemote: true + highlight: true + filterInput: 'input#search' + search: + fields: ['text'] + data: (term, callback) -> + $.ajax + url: self.autocompletePath + self.query + data: + term: term + beforeSend: -> + # dropdownMenu.addClass 'is-loading' + success: (response) -> + data = [] + + # Save groups ordering according to server response + groupNames = _.unique(_.pluck(response, 'category')) + + # Group results by category name + groups = _.groupBy response, (item) -> + item.category + + # List results + for groupName in groupNames + + # Add group header before list each group + data.push + header: groupName + + # List group + for item in groups[groupName] + data.push + text: item.label + url: item.url + + callback(data) + complete: -> + # dropdownMenu.removeClass 'is-loading' + + ) + + @dropdown.addClass('open') + @searchInput.removeClass('disabled') + @autocomplete = true; + + onDropdownOpen: (e) => + @dropdown.dropdown('toggle') + + onSearchInputKeyDown: (e) => + # Remove tag when pressing backspace and input search is empty + if e.keyCode is KEYCODE.BACKSPACE and e.currentTarget.value is '' + @removeLocationBadge() + @searchInput.focus() + + else if e.keyCode is KEYCODE.ESCAPE + @searchInput.val('') + @restoreOriginalState() + else + # Create new autocomplete if it hasn't been created yet and there's no badge + if @autocomplete is undefined + if !@badgePresent() + @enableAutocomplete() + else + # There's a badge + if @badgePresent() + @disableAutocomplete() + + onSearchInputFocus: => + @wrap.addClass('search-active') + + onSearchInputBlur: => + @wrap.removeClass('search-active') + + # If input is blank then restore state + if @searchInput.val() is '' + @restoreOriginalState() + + addLocationBadge: (item) -> + category = if item.category? then "#{item.category}: " else '' + value = if item.value? then item.value else '' + + html = " + #{category}#{value} + x + " + @locationBadgeEl.html(html) + restoreOriginalState: -> inputs = Object.keys @originalState @@ -51,122 +168,14 @@ class @SearchAutocomplete value: @originalState._location ) - serializeState: -> - { - # Search Criteria - project_id: @projectInputEl.val() - group_id: @groupInputEl.val() - search_code: @searchCodeInputEl.val() - repository_ref: @repositoryInputEl.val() + @dropdown.removeClass 'open' - # Location badge - _location: $.trim(@locationText.text()) - } + # Only add class if there's a badge + if @badgePresent() + @searchInput.addClass 'disabled' - createAutocomplete: -> - @query = "?project_id=" + @projectId + "&project_ref=" + @projectRef - - @searchInput.catcomplete - appendTo: 'form.navbar-form' - source: @autocompletePath + @query - minLength: 1 - maxShowItems: 15 - position: - # { my: "left top", at: "left bottom", collision: "none" } - my: "left-10 top+9" - at: "left bottom" - collision: "none" - close: (e) -> - e.preventDefault() - - select: (event, ui) => - # Pressing enter choses an alternative - if event.keyCode is @keyCode.ENTER - @goToResult(ui.item) - else - # Pressing tab sets the location - if event.keyCode is @keyCode.TAB and ui.item.location? - @setLocationBadge(ui.item) - @searchInput - .val('') # remove selected value from input - .focus() - else - # If option is not a location go to page - @goToResult(ui.item) - - # Return false to avoid focus on the next element - return false - - @autocomplete = @searchInput.data 'customCatcomplete' - - bindEvents: -> - @searchInput.on 'keydown', @onSearchInputKeyDown - @searchInput.on 'focus', @onSearchInputFocus - @searchInput.on 'blur', @onSearchInputBlur - @wrap.on 'click', '.remove-badge', @onRemoveLocationBadgeClick - - onRemoveLocationBadgeClick: (e) => - e.preventDefault() - @removeLocationBadge() - @searchInput.focus() - - onSearchInputKeyDown: (e) => - # Remove tag when pressing backspace and input search is empty - if e.keyCode is @keyCode.BACKSPACE and e.currentTarget.value is '' - @removeLocationBadge() - # @destroyAutocomplete() - @searchInput.focus() - else if e.keyCode is @keyCode.ESCAPE - @restoreOriginalState() - else - # Create new autocomplete if hasn't been created yet and there's no badge - if @autocomplete is undefined - if !@locationBadgeEl.children().length - @createAutocomplete() - else - # There's a badge - if @locationBadgeEl.children().length - @destroyAutocomplete() - - onSearchInputFocus: => - @wrap.addClass('search-active') - - onSearchInputBlur: => - @wrap.removeClass('search-active') - - # If input is blank then restore state - @restoreOriginalState() if @searchInput.val() is '' - - addLocationBadge: (item) -> - category = if item.category? then "#{item.category}: " else '' - value = if item.value? then item.value else '' - - html = " - #{category}#{value} - x - " - @locationBadgeEl.html(html) - - setLocationBadge: (item) -> - @addLocationBadge(item) - - # Reset input states - @resetSearchState() - - switch item.location - when 'projects' - @projectInputEl.val(item.id) - # @searchCodeInputEl.val('true') # TODO: always true for projects? - # @repositoryInputEl.val('master') # TODO: always master? - - when 'groups' - @groupInputEl.val(item.id) - - removeLocationBadge: -> - @locationBadgeEl.empty() - - # Reset state - @resetSearchState() + badgePresent: -> + @locationBadgeEl.children().length resetSearchState: -> # Remove scope @@ -184,10 +193,13 @@ class @SearchAutocomplete # Remove repository ref @repositoryInputEl.val('') - goToResult: (result) -> - location.href = result.url + removeLocationBadge: -> + @locationBadgeEl.empty() - destroyAutocomplete: -> - @autocomplete.destroy() if @autocomplete isnt undefined - @searchInput.attr('autocomplete', 'off') + # Reset state + @resetSearchState() + + disableAutocomplete: -> + if @autocomplete isnt undefined + @searchInput.addClass('disabled') @autocomplete = undefined diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index e0d655d305..7af307940d 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -51,12 +51,6 @@ margin: 0; } } - - .ui-autocomplete-category { - text-transform: uppercase; - font-size: 11px; - color: #7f8fa4; - } } .ui-state-default { diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index fa45d750f2..4a02f75719 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -21,7 +21,6 @@ } } - .search { margin-right: 10px; margin-left: 10px; @@ -51,7 +50,6 @@ padding: 4px; width: 350px; line-height: 24px; - overflow: hidden; } .location-text { @@ -69,7 +67,7 @@ padding: 0; margin-left: 5px; line-height: 25px; - width: 100%; + width: 98%; } .location-badge { @@ -89,7 +87,7 @@ } .search-location-badge, .search-input-wrap { - // Fallback if flex is not supported + // Fallback if flexbox is not supported display: inline-block; } @@ -103,6 +101,7 @@ position: absolute; right: 5px; color: #E7E9ED; + top: 0; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -114,9 +113,9 @@ } } - .ui-autocomplete-loading + .search-icon { - @extend .fa-spinner; - @extend .fa-spin; + .dropdown-header { + text-transform: uppercase; + font-size: 11px; } } } diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index a004908fb6..f051e7a186 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -4,7 +4,11 @@ .search-location-badge = render 'shared/location_badge' .search-input-wrap - = search_field_tag "search", nil, placeholder: 'Search', class: "search-input", spellcheck: false, tabindex: "1", autocomplete: 'off' + .dropdown{ data: {url: search_autocomplete_path } } + = search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' } + .dropdown-menu.dropdown-select + = dropdown_content + = dropdown_loading %i.search-icon = hidden_field_tag :group_id, @group.try(:id) From 73e043d196da3e9865677ec269b25a0a5178ed9b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 14 Mar 2016 16:23:41 -0500 Subject: [PATCH 151/153] Delete unused file --- .../lib/category_autocomplete.js.coffee | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 app/assets/javascripts/lib/category_autocomplete.js.coffee diff --git a/app/assets/javascripts/lib/category_autocomplete.js.coffee b/app/assets/javascripts/lib/category_autocomplete.js.coffee deleted file mode 100644 index c85fabbcd5..0000000000 --- a/app/assets/javascripts/lib/category_autocomplete.js.coffee +++ /dev/null @@ -1,49 +0,0 @@ -$.widget( "custom.catcomplete", $.ui.autocomplete, - _create: -> - @_super(); - @widget().menu("option", "items", "> :not(.ui-autocomplete-category)") - - _renderMenu: (ul, items) -> - currentCategory = '' - $.each items, (index, item) => - if item.category isnt currentCategory - ul.append("
  • #{item.category}
  • ") - currentCategory = item.category - - li = @_renderItemData(ul, item) - - if item.category? - li.attr('aria-label', item.category + " : " + item.label) - - _renderItem: (ul, item) -> - # Highlight occurrences - item.label = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(this.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); - - return $( "
  • " ) - .data( "item.autocomplete", item ) - .append( "#{item.label}" ) - .appendTo( ul ); - - _resizeMenu: -> - if (isNaN(this.options.maxShowItems)) - return - - ul = this.menu.element.css(overflowX: '', overflowY: '', width: '', maxHeight: '') - - lis = ul.children('li').css('whiteSpace', 'nowrap'); - - if (lis.length > this.options.maxShowItems) - ulW = ul.prop('clientWidth') - - ul.css( - overflowX: 'hidden' - overflowY: 'auto' - maxHeight: lis.eq(0).outerHeight() * this.options.maxShowItems + 1 - ) - - barW = ulW - ul.prop('clientWidth'); - ul.width('+=' + barW); - - # Original code from jquery.ui.autocomplete.js _resizeMenu() - ul.outerWidth(Math.max(ul.outerWidth() + 1, this.element.outerWidth())); - ) From cdd7e1855e8fd7e35583454166fc21dbb9f31b10 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 14 Mar 2016 22:04:22 -0500 Subject: [PATCH 152/153] Fixes failing spec --- app/assets/javascripts/gl_dropdown.js.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 3d2e3f3dbb..ca7b22bd81 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -237,13 +237,12 @@ class GitLabDropdown html = @options.renderRow(data) else selected = if @options.isSelected then @options.isSelected(data) else false - url = if @options.url then @options.url(data) else "#" # Set URL if @options.url? url = @options.url(data) else - url = if data.url? then data.url else '' + url = if data.url? then data.url else '#' # Set Text if @options.text? From 46f9790ceb30cf00a3f9d11b8ef0121294fc1a40 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Fri, 18 Mar 2016 14:06:08 -0400 Subject: [PATCH 153/153] Fixing rebase conflicts --- app/assets/stylesheets/framework/jquery.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 7af307940d..eb3fbd9155 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -19,8 +19,6 @@ } &.ui-autocomplete { - border-color: #ddd; - padding: 0; margin-top: 2px; z-index: 1001; width: 240px;